Skip to content

Commit

Permalink
Merge pull request #32 from joel-u410/joel-u410/json-marshal-for-result
Browse files Browse the repository at this point in the history
Add MarshalJSON for Result[T]
  • Loading branch information
samber authored Sep 28, 2023
2 parents 1e785d6 + e7ccd89 commit 77f2b29
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 1 deletion.
53 changes: 52 additions & 1 deletion result.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package mo

import "fmt"
import (
"encoding/json"
"errors"
"fmt"
)

// Ok builds a Result when value is valid.
// Play: https://go.dev/play/p/PDwADdzNoyZ
Expand Down Expand Up @@ -162,3 +166,50 @@ func (r Result[T]) FlatMap(mapper func(value T) Result[T]) Result[T] {

return Err[T](r.err)
}

// MarshalJSON encodes Result into json, following the JSON-RPC specification for results,
// with one exception: when the result is an error, the "code" field is not included.
// Reference: https://www.jsonrpc.org/specification
func (o Result[T]) MarshalJSON() ([]byte, error) {
if o.isErr {
return json.Marshal(map[string]any{
"error": map[string]any{
"message": o.err.Error(),
},
})
}

return json.Marshal(map[string]any{
"result": o.value,
})
}

// UnmarshalJSON decodes json into Result. If "error" is set, the result is an
// Err containing the error message as a generic error object. Otherwise, the
// result is an Ok containing the result. If the JSON object contains netiher
// an error nor a result, the result is an Ok containing an empty value. If the
// JSON object contains both an error and a result, the result is an Err. Finally,
// if the JSON object contains an error but is not structured correctly (no message
// field), the unmarshaling fails.
func (o *Result[T]) UnmarshalJSON(data []byte) error {
var result struct {
Result T `json:"result"`
Error struct {
Message string `json:"message"`
} `json:"error"`
}

if err := json.Unmarshal(data, &result); err != nil {
return err
}

if result.Error.Message != "" {
o.err = errors.New(result.Error.Message)
o.isErr = true
return nil
}

o.value = result.Result
o.isErr = false
return nil
}
93 changes: 93 additions & 0 deletions result_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mo

import (
"encoding/json"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -201,3 +203,94 @@ func TestResultFlatMap(t *testing.T) {
is.Equal(Result[int]{value: 42, isErr: false, err: nil}, opt1)
is.Equal(Result[int]{value: 0, isErr: true, err: assert.AnError}, opt2)
}

func TestResultMarshalJSON(t *testing.T) {
is := assert.New(t)

result1 := Ok("foo")
result2 := Err[string](fmt.Errorf("an error"))
result3 := Ok("")

value, err := result1.MarshalJSON()
is.NoError(err)
is.Equal(`{"result":"foo"}`, string(value))

value, err = result2.MarshalJSON()
is.NoError(err)
is.Equal(`{"error":{"message":"an error"}}`, string(value))

value, err = result3.MarshalJSON()
is.NoError(err)
is.Equal(`{"result":""}`, string(value))

type testStruct struct {
Field Result[string]
}

resultInStruct := testStruct{
Field: result1,
}
var marshalled []byte
marshalled, err = json.Marshal(resultInStruct)
is.NoError(err)
is.Equal(`{"Field":{"result":"foo"}}`, string(marshalled))
}

func TestResultUnmarshalJSON(t *testing.T) {
is := assert.New(t)

result1 := Ok("foo")
result2 := Err[string](fmt.Errorf("an error"))
result3 := Ok("")

err := result1.UnmarshalJSON([]byte(`{"result":"foo"}`))
is.NoError(err)
is.Equal(Ok("foo"), result1)

var res Result[string]
err = json.Unmarshal([]byte(`{"result":"foo"}`), &res)
is.NoError(err)
is.Equal(res, result1)

err = result2.UnmarshalJSON([]byte(`{"error":{"message":"an error"}}`))
is.NoError(err)
is.Equal(Err[string](fmt.Errorf("an error")), result2)

err = result3.UnmarshalJSON([]byte(`{"result":""}`))
is.NoError(err)
is.Equal(Ok(""), result3)

type testStruct struct {
Field Result[string]
}

unmarshal := testStruct{}
err = json.Unmarshal([]byte(`{"Field":{"result":"foo"}}`), &unmarshal)
is.NoError(err)
is.Equal(testStruct{Field: Ok("foo")}, unmarshal)

unmarshal = testStruct{}
err = json.Unmarshal([]byte(`{"Field":{"error":{"message":"an error"}}}`), &unmarshal)
is.NoError(err)
is.Equal(testStruct{Field: Err[string](fmt.Errorf("an error"))}, unmarshal)

unmarshal = testStruct{}
err = json.Unmarshal([]byte(`{}`), &unmarshal)
is.NoError(err)
is.Equal(testStruct{Field: Ok("")}, unmarshal)

// Both result and error are set; unmarshal to Err
unmarshal = testStruct{}
err = json.Unmarshal([]byte(`{"Field":{"result":"foo","error":{"message":"an error"}}}`), &unmarshal)
is.NoError(err)
is.Equal(testStruct{Field: Err[string](fmt.Errorf("an error"))}, unmarshal)

// Bad structure for error; cannot unmarshal
unmarshal = testStruct{}
err = json.Unmarshal([]byte(`{"Field":{"result":"foo","error":true}}`), &unmarshal)
is.Error(err)

unmarshal = testStruct{}
err = json.Unmarshal([]byte(`{"Field": "}`), &unmarshal)
is.Error(err)
}

0 comments on commit 77f2b29

Please sign in to comment.