Skip to content

Commit

Permalink
feat(receipt, result): easier receipt utils
Browse files Browse the repository at this point in the history
provides a number of simple improvements working with receipts
  • Loading branch information
hannahhoward committed Oct 25, 2024
1 parent 7edcd37 commit 704b565
Show file tree
Hide file tree
Showing 9 changed files with 665 additions and 13 deletions.
21 changes: 21 additions & 0 deletions core/receipt/datamodel/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package datamodel
import (
"bytes"
_ "embed"
"errors"
"fmt"

"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/schema"
schemadmt "github.com/ipld/go-ipld-prime/schema/dmt"
schemadsl "github.com/ipld/go-ipld-prime/schema/dsl"
)

//go:embed receipt.ipldsch
Expand Down Expand Up @@ -82,3 +85,21 @@ func NewReceiptModelType(resultschema []byte) (schema.Type, error) {
}
return ts.TypeByName("Receipt"), nil
}

func NewReceiptModelFromTypes(successType schema.Type, errType schema.Type) (schema.Type, error) {
ts := new(schema.TypeSystem)
ts.Init()
schema.SpawnDefaultBasicTypes(ts)
schema.MergeTypeSystem(ts, successType.TypeSystem(), true)
schema.MergeTypeSystem(ts, errType.TypeSystem(), true)
ts.Accumulate(schema.SpawnUnion("Result", []schema.TypeName{successType.Name(), errType.Name()}, schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{"ok": successType.Name(), "error": errType.Name()})))
sch, err := schemadsl.Parse("", bytes.NewReader(receipt))
if err != nil {
return nil, err
}
schemadmt.SpawnSchemaTypes(ts, sch)
if errs := ts.ValidateGraph(); errs != nil {
return nil, errors.Join(errs...)
}
return ts.TypeByName("Receipt"), nil
}
70 changes: 70 additions & 0 deletions core/receipt/datamodel/receipt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/storacha/go-ucanto/core/ipld/block"
"github.com/storacha/go-ucanto/core/ipld/codec/cbor"
Expand Down Expand Up @@ -87,3 +88,72 @@ func TestEncodeDecode(t *testing.T) {
t.Fatalf("error message was not boom")
}
}

func TestEncodeDecoderFromTypes(t *testing.T) {
ts, err := ipld.LoadSchemaBytes([]byte(`
type Ok struct {
status String (rename "Status")
}
type Err struct {
message String (rename "Message")
}
`))
if err != nil {
t.Fatalf("loading schemas: %s", err)
}

typ, err := rdm.NewReceiptModelFromTypes(ts.TypeByName("Ok"), ts.TypeByName("Err"))
if err != nil {
t.Fatalf("loading result schema: %s", err)
}

l := cidlink.Link{Cid: cid.MustParse("bafkreiem4twkqzsq2aj4shbycd4yvoj2cx72vezicletlhi7dijjciqpui")}
r0 := rdm.ReceiptModel[resultOk, resultErr]{
Ocm: rdm.OutcomeModel[resultOk, resultErr]{
Ran: l,
Out: rdm.ResultModel[resultOk, resultErr]{
Ok: &resultOk{Status: "done"},
},
},
}
b0, err := block.Encode(&r0, typ, cbor.Codec, sha256.Hasher)
if err != nil {
t.Fatalf("encoding receipt: %s", err)
}
r1 := rdm.ReceiptModel[resultOk, resultErr]{}
err = block.Decode(b0, &r1, typ, cbor.Codec, sha256.Hasher)
if err != nil {
t.Fatalf("decoding receipt: %s", err)
}
if r1.Ocm.Out.Err != nil {
t.Fatalf("result err was not nil")
}
if r1.Ocm.Out.Ok.Status != "done" {
t.Fatalf("status was not done")
}

r2 := rdm.ReceiptModel[resultOk, resultErr]{
Ocm: rdm.OutcomeModel[resultOk, resultErr]{
Ran: l,
Out: rdm.ResultModel[resultOk, resultErr]{
Err: &resultErr{Message: "boom"},
},
},
}
b1, err := block.Encode(&r2, typ, cbor.Codec, sha256.Hasher)
if err != nil {
t.Fatalf("encoding receipt: %s", err)
}
r3 := rdm.ReceiptModel[resultOk, resultErr]{}
err = block.Decode(b1, &r3, typ, cbor.Codec, sha256.Hasher)
if err != nil {
t.Fatalf("decoding receipt: %s", err)
}
if r3.Ocm.Out.Ok != nil {
t.Fatalf("result ok was not nil")
}
if r3.Ocm.Out.Err.Message != "boom" {
t.Fatalf("error message was not boom")
}
}
8 changes: 8 additions & 0 deletions core/receipt/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,14 @@ func NewReceiptReader[O, X any](resultschema []byte) (ReceiptReader[O, X], error
return &receiptReader[O, X]{typ}, nil
}

func NewReceiptReaderFromTypes[O, X any](successType schema.Type, errType schema.Type) (ReceiptReader[O, X], error) {
typ, err := rdm.NewReceiptModelFromTypes(successType, errType)
if err != nil {
return nil, fmt.Errorf("loading receipt data model: %s", err)
}
return &receiptReader[O, X]{typ}, nil
}

type AnyReceipt Receipt[ipld.Node, ipld.Node]

// Option is an option configuring a UCAN delegation.
Expand Down
4 changes: 4 additions & 0 deletions core/result/failure/datamodel/failure.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ func init() {
typ = ts.TypeByName("Failure")
}

func FailureType() schema.Type {
return typ
}

func Schema() []byte {
return failureSchema
}
4 changes: 4 additions & 0 deletions core/result/failure/faillure.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,7 @@ func FromError(err error) IPLDBuilderFailure {
}
return fail
}

func FromFailureModel(model datamodel.FailureModel) IPLDBuilderFailure {
return failure{model: model}
}
10 changes: 10 additions & 0 deletions core/result/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,16 @@ func Wrap[O any, X comparable](inner func() (O, X)) Result[O, X] {
return Ok[O, X](o)
}

func Unwrap[O any, X any](result Result[O, X]) (O, X) {
return MatchResultR2(result, func(ok O) (O, X) {
var err X
return ok, err
}, func(err X) (O, X) {
var ok O
return ok, err
})
}

func NewFailure(err error) Result[ipld.Builder, ipld.Builder] {
if ipldConvertableError, ok := err.(failure.IPLDConvertableError); ok {
return Error[ipld.Builder, ipld.Builder](ipldConvertableError)
Expand Down
19 changes: 17 additions & 2 deletions core/result/result_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,17 +158,32 @@ func TestWrap(t *testing.T) {
require.Equal(t,
result.Error[int](errors.New("bad")),
result.Wrap(func() (int, error) { return 0, errors.New("bad") }),
"string (no error)")
"int (error)")
require.Equal(t,
result.Ok[string, error]("apple"),
result.Wrap(func() (string, error) { return "apple", nil }),
"int (no error)")
"string (no error)")
require.Equal(t,
result.Error[string](errors.New("bad")),
result.Wrap(func() (string, error) { return "", errors.New("bad") }),
"string (error present)")
}

func TestWrapUnwrap(t *testing.T) {
val, err := result.Unwrap(result.Wrap(func() (int, error) { return 5, nil }))
require.Equal(t, 5, val, "int (no error)")
require.NoError(t, err, "int (no error)")
val, err = result.Unwrap(result.Wrap(func() (int, error) { return 0, errors.New("bad") }))
require.EqualError(t, err, "bad", "int (error)")
require.Equal(t, 0, val, "int (error)")
str, err := result.Unwrap(result.Wrap(func() (string, error) { return "apple", nil }))
require.Equal(t, "apple", str, "string (no error)")
require.NoError(t, err, "string (no error)")
str, err = result.Unwrap(result.Wrap(func() (string, error) { return "", errors.New("bad") }))
require.EqualError(t, err, "bad", "string (error)")
require.Equal(t, "", str, "string (error)")
}

func TestAndOr(t *testing.T) {
t.Run("And", func(t *testing.T) {
testAnd(t, "O - int, O2 - string, X - error (no error)",
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/ipfs/go-cid v0.4.1
github.com/ipfs/go-ipld-cbor v0.1.0
github.com/ipld/go-car v0.6.2
github.com/ipld/go-ipld-prime v0.21.0
github.com/ipld/go-ipld-prime v0.21.1-0.20240917223228-6148356a4c2e
github.com/multiformats/go-base32 v0.1.0
github.com/multiformats/go-multibase v0.2.0
github.com/multiformats/go-multihash v0.2.3
Expand Down Expand Up @@ -66,7 +66,7 @@ require (
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/polydawn/refmt v0.89.0 // indirect
github.com/polydawn/refmt v0.89.1-0.20231129105047-37766d95467a // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/whyrusleeping/cbor-gen v0.1.2 // indirect
go.opentelemetry.io/otel v1.30.0 // indirect
Expand Down
Loading

0 comments on commit 704b565

Please sign in to comment.