Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(receipt, result): easier receipt utils #29

Merged
merged 1 commit into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading