Commit a2ea4706 authored by Eric Myhre's avatar Eric Myhre

Yank TypedNode interface into schema package.

Previously it was in the 'impl/typed' package, next to the
runtime-wrapper implementation of the interface.  This was strange.

Not only should those two things be separated just on principle,
this was also causing more import cycle problems down the road:
for example, the traversal package needs to consider the *interface*
for a schema-typed node in order to gracefully handle some features...
and if this also brings in a *concrete* dependency on the
runtime-wrapper implementation of typed nodes, not only is that
incorrect bloat, it becomes a show stopper because (currently, at
least) that implementation also in turn transitively imports the
ipldfree package for some of its scalars.  Ouchouch.

So.  Now the interface lives over in the 'schema' package, with all
the other interfaces for that feature set.  Where it probably always
should have been.

('typed.Maybe' also became known as 'schema.Maybe', which... does not
roll off the tongue as nicely.  But this is a minor concern and we
might reconsider the naming and appearance of that thing later anyway.)
parent 50dfd994
......@@ -35,8 +35,8 @@ or new codecs, or new higher-order order functions!)
- `github.com/ipld/go-ipld-prime/encoding/dagcbor` -- implementations of marshalling and unmarshalling as CBOR (a fast, binary serialization format).
- `github.com/ipld/go-ipld-prime/encoding/dagjson` -- implementations of marshalling and unmarshalling as JSON (a popular human readable format).
- `github.com/ipld/go-ipld-prime/linking/cid` -- imported as `cidlink` -- provides concrete implementations of `Link` as a CID. Also, the multicodec registry.
- `github.com/ipld/go-ipld-prime/schema` -- contains the `schema.Type` declarations, which represent IPLD Schema type information.
- `github.com/ipld/go-ipld-prime/impl/typed` -- contains the `typed.Node` interface, which enhances the basic `Node` to have additional features described by IPLD Schemas.
- `github.com/ipld/go-ipld-prime/schema` -- contains the `schema.Type` and `schema.TypedNode` interface declarations, which represent IPLD Schema type information.
- `github.com/ipld/go-ipld-prime/impl/typed` -- provides concrete implementations of `schema.TypedNode` which decorate a basic `Node` at runtime to have additional features described by IPLD Schemas.
......
......@@ -53,7 +53,7 @@ func Unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource) error {
// starts with the first token already primed. Necessary to get recursion
// to flow right without a peek+unpeek system.
func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token) error {
// FUTURE: check for typed.NodeBuilder that's going to parse a Link (they can slurp any token kind they want).
// FUTURE: check for schema.TypedNodeBuilder that's going to parse a Link (they can slurp any token kind they want).
switch tk.Type {
case tok.TMapOpen:
expectLen := tk.Length
......
......@@ -43,7 +43,7 @@ func (e ErrWrongKind) Error() string {
// ErrNotExists may be returned from the lookup functions of the Node interface
// to indicate a missing value.
//
// Note that typed.ErrNoSuchField is another type of error which sometimes
// Note that schema.ErrNoSuchField is another type of error which sometimes
// occurs in similar places as ErrNotExists. ErrNoSuchField is preferred
// when handling data with constraints provided by a schema that mean that
// a field can *never* exist (as differentiated from a map key which is
......
......@@ -71,12 +71,12 @@ type Node interface {
// Lookup is the equivalent of LookupString, but takes a reified Node
// as a parameter instead of a plain string.
// This mechanism is useful if working with typed maps (if the key types
// have constraints, and you already have a reified `typed.Node` value,
// have constraints, and you already have a reified `schema.TypedNode` value,
// using that value can save parsing and validation costs);
// and may simply be convenient if you already have a Node value in hand.
//
// (When writing generic functions over Node, a good rule of thumb is:
// when handling a map, check for `typed.Node`, and in this case prefer
// when handling a map, check for `schema.TypedNode`, and in this case prefer
// the Lookup(Node) method; otherwise, favor LookupString; typically
// implementations will have their fastest paths thusly.)
Lookup(key Node) (Node, error)
......@@ -126,7 +126,7 @@ type Node interface {
// Undefined nodes are returned when traversing a struct field that is
// defined by a schema but unset in the data. (Undefined nodes are not
// possible otherwise; you'll only see them from `typed.Node`.)
// possible otherwise; you'll only see them from `schema.TypedNode`.)
// The undefined flag is necessary so iterating over structs can
// unambiguously make the distinction between values that are
// present-and-null versus values that are absent.
......
......@@ -26,7 +26,7 @@ Concerns:
- 2. Performance
A `Node` implementation must of course conform with the Data Model.
Some nodes (especially, those that also implement `typed.Node`) may also
Some nodes (especially, those that also implement `schema.TypedNode`) may also
have additional constraints.
A `Node` implementation must maintain immutablity, or it shatters abstractions
......@@ -70,7 +70,7 @@ Castability for strings is safe when the `Node` is "general" (i.e. has no constr
With no constraints, there's no Correctness concern;
and since strings are immutable, there's no Immutablity concern.
Castability for strings is often *unsafe* when the `Node` is a `typed.Node`.
Castability for strings is often *unsafe* when the `Node` is a `schema.TypedNode`.
Typed nodes may have additional constraints, so we would have a Correctness problem.
(Note that the way we handle constraints in codegeneration means users can add
them *after* the code is generated, so the generation system can't presume
......@@ -94,7 +94,7 @@ If the struct type is unexported, the concern is absolved:
the zero value can't be initialized outside the package.
If the `Node` implementation has no other constraints
(e.g., it's not also a `typed.Node` in addition to just an `ipld.Node`),
(e.g., it's not also a `schema.TypedNode` in addition to just an `ipld.Node`),
the concern is (alomst certainly) absolved:
the zero value is simply a valid value.
......
......@@ -98,7 +98,7 @@ allowed to contain pointers (including cyclic references), etc.
The reified schema can be computed purely from the schema declaration.
The reified schema implementation lives in `go-ipld-prime//typed/system`,
and `go-ipld-prime//typed.Node` notably has a `Type() typesystem.Type` method
and `go-ipld-prime//schema.TypedNode` notably has a `Type() typesystem.Type` method
which provides the reified schema information for any typed node.
Note that multiple disjoint `typesystem.Universe` instances can exist in the
......
......@@ -36,7 +36,7 @@ func Unmarshal(nb ipld.NodeBuilder, tokSrc shared.TokenSource) (ipld.Node, error
// starts with the first token already primed. Necessary to get recursion
// to flow right without a peek+unpeek system.
func unmarshal(nb ipld.NodeBuilder, tokSrc shared.TokenSource, tk *tok.Token) (ipld.Node, error) {
// FUTURE: check for typed.NodeBuilder that's going to parse a Link (they can slurp any token kind they want).
// FUTURE: check for schema.TypedNodeBuilder that's going to parse a Link (they can slurp any token kind they want).
switch tk.Type {
case tok.TMapOpen:
mb, err := nb.CreateMap()
......
......@@ -31,7 +31,7 @@ func Unmarshal(nb ipld.NodeBuilder, tokSrc shared.TokenSource) (ipld.Node, error
// starts with the first token already primed. Necessary to get recursion
// to flow right without a peek+unpeek system.
func unmarshal(nb ipld.NodeBuilder, tokSrc shared.TokenSource, tk *tok.Token) (ipld.Node, error) {
// FUTURE: check for typed.NodeBuilder that's going to parse a Link (they can slurp any token kind they want).
// FUTURE: check for schema.TypedNodeBuilder that's going to parse a Link (they can slurp any token kind they want).
switch tk.Type {
case tok.TMapOpen:
mb, err := nb.CreateMap()
......
......@@ -42,7 +42,7 @@ func Unmarshal(nb ipld.NodeBuilder, tokSrc shared.TokenSource) (ipld.Node, error
// starts with the first token already primed. Necessary to get recursion
// to flow right without a peek+unpeek system.
func unmarshal(nb ipld.NodeBuilder, tokSrc shared.TokenSource, tk *tok.Token) (ipld.Node, error) {
// FUTURE: check for typed.NodeBuilder that's going to parse a Link (they can slurp any token kind they want).
// FUTURE: check for schema.TypedNodeBuilder that's going to parse a Link (they can slurp any token kind they want).
switch tk.Type {
case tok.TMapOpen:
mb, err := nb.CreateMap()
......
......@@ -43,7 +43,7 @@ func (e ErrWrongKind) Error() string {
// ErrNotExists may be returned from the lookup functions of the Node interface
// to indicate a missing value.
//
// Note that typed.ErrNoSuchField is another type of error which sometimes
// Note that schema.ErrNoSuchField is another type of error which sometimes
// occurs in similar places as ErrNotExists. ErrNoSuchField is preferred
// when handling data with constraints provided by a schema that mean that
// a field can *never* exist (as differentiated from a map key which is
......@@ -64,7 +64,7 @@ func (e ErrNotExists) Error() string {
type ErrInvalidKey struct {
Reason string
// Perhaps typed.ErrNoSuchField could be folded into this?
// Perhaps schema.ErrNoSuchField could be folded into this?
// Perhaps Reason could be replaced by an enum of "NoSuchField"|"NotAString"|"ConstraintRejected"?
// Might be hard to get rid of the freetext field entirely -- constraints may be nontrivial to describe.
}
......
......@@ -81,7 +81,7 @@ func (nb *nodeBuilder) CreateMap(fn MapBuildingClosure) ipld.Node {
if err != nil {
panic(Error{err})
}
fn(mapBuilder{mb}, nb, nb) // FUTURE: check for typed.NodeBuilder; need to specialize latter params before calling down if so.
fn(mapBuilder{mb}, nb, nb) // FUTURE: check for schema.TypedNodeBuilder; need to specialize latter params before calling down if so.
n, err := mb.Build()
if err != nil {
panic(Error{err})
......@@ -93,7 +93,7 @@ func (nb *nodeBuilder) AmendMap(fn MapBuildingClosure) ipld.Node {
if err != nil {
panic(Error{err})
}
fn(mapBuilder{mb}, nb, nb) // FUTURE: check for typed.NodeBuilder; need to specialize latter params before calling down if so.
fn(mapBuilder{mb}, nb, nb) // FUTURE: check for schema.TypedNodeBuilder; need to specialize latter params before calling down if so.
n, err := mb.Build()
if err != nil {
panic(Error{err})
......@@ -105,7 +105,7 @@ func (nb *nodeBuilder) CreateList(fn ListBuildingClosure) ipld.Node {
if err != nil {
panic(Error{err})
}
fn(listBuilder{lb}, nb) // FUTURE: check for typed.NodeBuilder; need to specialize latter params before calling down if so.
fn(listBuilder{lb}, nb) // FUTURE: check for schema.TypedNodeBuilder; need to specialize latter params before calling down if so.
n, err := lb.Build()
if err != nil {
panic(Error{err})
......@@ -117,7 +117,7 @@ func (nb *nodeBuilder) AmendList(fn ListBuildingClosure) ipld.Node {
if err != nil {
panic(Error{err})
}
fn(listBuilder{lb}, nb) // FUTURE: check for typed.NodeBuilder; need to specialize latter params before calling down if so.
fn(listBuilder{lb}, nb) // FUTURE: check for schema.TypedNodeBuilder; need to specialize latter params before calling down if so.
n, err := lb.Build()
if err != nil {
panic(Error{err})
......
......@@ -2,7 +2,7 @@ package typed
import "github.com/ipld/go-ipld-prime"
// typed.LinkNode is a superset of the typed.Node interface, and has one additional behavior.
// typed.LinkNode is a superset of the schema.TypedNode interface, and has one additional behavior.
//
// A typed.LinkNode contains a hint for the appropriate node builder to use for loading data
// on the other side of the link contained within the node, so that it can be assembled
......
......@@ -8,7 +8,7 @@ import (
"github.com/ipld/go-ipld-prime/schema"
)
var _ Node = wrapnodeStruct{}
var _ schema.TypedNode = wrapnodeStruct{}
type wrapnodeStruct struct {
ipld.Node
......@@ -16,7 +16,7 @@ type wrapnodeStruct struct {
}
// Most of the 'nope' methods from the inner node are fine;
// we add the extra things required for typed.Node;
// we add the extra things required for schema.TypedNode;
// we decorate the getters and iterators to handle the distinct path around optionals
// and return a different error for missing fields;
// length becomes fixed to a constant;
......@@ -42,7 +42,7 @@ func (tn wrapnodeStruct) LookupString(key string) (ipld.Node, error) {
}
return nil, e1
}
return nil, ErrNoSuchField{Type: tn.typ, FieldName: key}
return nil, schema.ErrNoSuchField{Type: tn.typ, FieldName: key}
}
func (tn wrapnodeStruct) MapIterator() ipld.MapIterator {
......@@ -168,10 +168,10 @@ func (mb *wrapnodeStruct_MapBuilder) Insert(k, v ipld.Node) error {
// Check that the field exists at all.
field := mb.typ.Field(ks)
if field == nil {
return ErrNoSuchField{Type: mb.typ, FieldName: ks}
return schema.ErrNoSuchField{Type: mb.typ, FieldName: ks}
}
// Check that the value is assignable to this field, or return error.
vt, ok := v.(Node)
vt, ok := v.(schema.TypedNode)
switch {
case v.IsNull():
if !field.IsNullable() {
......@@ -184,7 +184,7 @@ func (mb *wrapnodeStruct_MapBuilder) Insert(k, v ipld.Node) error {
}
// if typed node, and it matches: carry on.
default:
return fmt.Errorf("need typed.Node for insertion into struct") // FUTURE: maybe if it's a basic enough thing we sholud attempt coerce?
return fmt.Errorf("need schema.TypedNode for insertion into struct") // FUTURE: maybe if it's a basic enough thing we sholud attempt coerce?
}
// Insert the value, and note it's now been set.
if err := mb.utmb.Insert(k, v); err != nil {
......
......@@ -18,7 +18,7 @@ package must
import (
ipld "github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/impl/typed"
"github.com/ipld/go-ipld-prime/schema"
)
// must.NotError simply panics if given an error.
......@@ -47,18 +47,18 @@ func Node(n ipld.Node, e error) ipld.Node {
// must.TypedNode helps write pointfree/chainable-style code
// by taking a Node and an error and transforming any error into a panic.
// It will also cast the `ipld.Node` to a `typed.Node`, panicking if impossible.
// It will also cast the `ipld.Node` to a `schema.TypedNode`, panicking if impossible.
//
// Because golang supports implied destructuring of multiple-return functions
// into arguments for another funtion of matching arity, it can be used like this:
//
// must.TypedNode(SomeNodeBuilder{}.CreateString("a"))
//
func TypedNode(n ipld.Node, e error) typed.Node {
func TypedNode(n ipld.Node, e error) schema.TypedNode {
if e != nil {
panic(e)
}
return n.(typed.Node)
return n.(schema.TypedNode)
}
// must.True panics if the given bool is false.
......
......@@ -71,12 +71,12 @@ type Node interface {
// Lookup is the equivalent of LookupString, but takes a reified Node
// as a parameter instead of a plain string.
// This mechanism is useful if working with typed maps (if the key types
// have constraints, and you already have a reified `typed.Node` value,
// have constraints, and you already have a reified `schema.TypedNode` value,
// using that value can save parsing and validation costs);
// and may simply be convenient if you already have a Node value in hand.
//
// (When writing generic functions over Node, a good rule of thumb is:
// when handling a map, check for `typed.Node`, and in this case prefer
// when handling a map, check for `schema.TypedNode`, and in this case prefer
// the Lookup(Node) method; otherwise, favor LookupString; typically
// implementations will have their fastest paths thusly.)
Lookup(key Node) (Node, error)
......@@ -128,7 +128,7 @@ type Node interface {
// Undefined nodes are returned when traversing a struct field that is
// defined by a schema but unset in the data. (Undefined nodes are not
// possible otherwise; you'll only see them from `typed.Node`.)
// possible otherwise; you'll only see them from `schema.TypedNode`.)
// The undefined flag is necessary so iterating over structs can
// unambiguously make the distinction between values that are
// present-and-null versus values that are absent.
......
......@@ -31,10 +31,10 @@ package ipld
// `reflect.Type` handle it can use to create a new value of that native type;
// similarly, schema-typed Nodes will yield a NodeBuilder that keeps the schema
// info and type constraints from that Node!
// (Continuing the typed.Node example: if you have a typed.Node that is
// (Continuing the schema.TypedNode example: if you have a schema.TypedNode that is
// constrained to be of some `type Foo = {Bar:Baz}` type, then any new Node
// produced from its NodeBuilder will still answer
// `n.(typed.Node).Type().Name()` as `Foo`; and if
// `n.(schema.TypedNode).Type().Name()` as `Foo`; and if
// `n.NodeBuilder().AmendMap().Insert(...)` is called with nodes of unmatching
// type given to the insertion, the builder will error!)
//
......
package typed
package schema
import (
"fmt"
"github.com/ipld/go-ipld-prime/schema"
)
// ErrNoSuchField may be returned from lookup functions on the Node
// interface when a field is requested which doesn't exist, or from Insert
// on a MapBuilder when a key doesn't match a field name in the structure.
type ErrNoSuchField struct {
Type schema.Type
Type Type
FieldName string
}
......
......@@ -13,7 +13,7 @@ import (
"github.com/ipld/go-ipld-prime/encoding"
"github.com/ipld/go-ipld-prime/fluent"
ipldfree "github.com/ipld/go-ipld-prime/impl/free"
"github.com/ipld/go-ipld-prime/impl/typed"
"github.com/ipld/go-ipld-prime/schema"
)
// TokenSourceBucket acts like a TokenSource by yielding tokens from a pre-made
......@@ -65,7 +65,7 @@ func TestScalarUnmarshal(t *testing.T) {
func TestGeneratedStructs(t *testing.T) {
t.Run("struct with map repr", func(t *testing.T) {
var (
v0, v1, v2, v3, v4 typed.Node
v0, v1, v2, v3, v4 schema.TypedNode
)
t.Run("type-level build and read", func(t *testing.T) {
t.Run("all fields set", func(t *testing.T) {
......@@ -76,7 +76,7 @@ func TestGeneratedStructs(t *testing.T) {
mb.Insert(ipldfree.String("f3"), plz(String__NodeBuilder().CreateString("c")))
mb.Insert(ipldfree.String("f4"), plz(String__NodeBuilder().CreateString("d")))
n, err := mb.Build()
v0 = n.(typed.Node)
v0 = n.(schema.TypedNode)
Wish(t, err, ShouldEqual, nil)
Wish(t, n.ReprKind(), ShouldEqual, ipld.ReprKind_Map)
......@@ -93,7 +93,7 @@ func TestGeneratedStructs(t *testing.T) {
mb.Insert(ipldfree.String("f3"), plz(String__NodeBuilder().CreateString("c")))
mb.Insert(ipldfree.String("f4"), ipld.Null)
n, err := mb.Build()
v1 = n.(typed.Node)
v1 = n.(schema.TypedNode)
Wish(t, err, ShouldEqual, nil)
Wish(t, n.ReprKind(), ShouldEqual, ipld.ReprKind_Map)
......@@ -111,7 +111,7 @@ func TestGeneratedStructs(t *testing.T) {
mb.Insert(ipldfree.String("f3"), ipld.Null)
mb.Insert(ipldfree.String("f4"), plz(String__NodeBuilder().CreateString("d")))
n, err := mb.Build()
v2 = n.(typed.Node)
v2 = n.(schema.TypedNode)
Wish(t, err, ShouldEqual, nil)
Wish(t, n.ReprKind(), ShouldEqual, ipld.ReprKind_Map)
......@@ -128,7 +128,7 @@ func TestGeneratedStructs(t *testing.T) {
mb.Insert(ipldfree.String("f3"), plz(String__NodeBuilder().CreateString("c")))
mb.Insert(ipldfree.String("f4"), plz(String__NodeBuilder().CreateString("d")))
n, err := mb.Build()
v3 = n.(typed.Node)
v3 = n.(schema.TypedNode)
Wish(t, err, ShouldEqual, nil)
Wish(t, n.ReprKind(), ShouldEqual, ipld.ReprKind_Map)
......@@ -145,7 +145,7 @@ func TestGeneratedStructs(t *testing.T) {
mb.Insert(ipldfree.String("f2"), plz(String__NodeBuilder().CreateString("b")))
mb.Insert(ipldfree.String("f4"), plz(String__NodeBuilder().CreateString("d")))
n, err := mb.Build()
v4 = n.(typed.Node)
v4 = n.(schema.TypedNode)
Wish(t, err, ShouldEqual, nil)
Wish(t, n.ReprKind(), ShouldEqual, ipld.ReprKind_Map)
......
......@@ -30,7 +30,7 @@ type typedNodeGenerator interface {
EmitNativeBuilder(io.Writer) // typically emits some kind of struct that has a Build method.
EmitNativeMaybe(io.Writer) // a pointer-free 'maybe' mechanism is generated for all types.
// -- the typed.Node.Type method and vars -->
// -- the schema.TypedNode.Type method and vars -->
EmitTypedNodeMethodType(io.Writer) // these emit dummies for now
......@@ -100,7 +100,6 @@ func EmitFileHeader(packageName string, w io.Writer) {
fmt.Fprintf(w, "package %s\n\n", packageName)
fmt.Fprintf(w, "import (\n")
fmt.Fprintf(w, "\tipld \"github.com/ipld/go-ipld-prime\"\n")
fmt.Fprintf(w, "\t\"github.com/ipld/go-ipld-prime/impl/typed\"\n")
fmt.Fprintf(w, "\t\"github.com/ipld/go-ipld-prime/schema\"\n")
fmt.Fprintf(w, ")\n\n")
fmt.Fprintf(w, "// Code generated go-ipld-prime DO NOT EDIT.\n\n")
......
......@@ -46,12 +46,12 @@ func (gk generateKindBytes) EmitNativeMaybe(w io.Writer) {
// TODO this can most likely be extracted and DRY'd, just not 100% sure yet
doTemplate(`
type Maybe{{ .Type | mungeTypeNodeIdent }} struct {
Maybe typed.Maybe
Maybe schema.Maybe
Value {{ .Type | mungeTypeNodeIdent }}
}
func (m Maybe{{ .Type | mungeTypeNodeIdent }}) Must() {{ .Type | mungeTypeNodeIdent }} {
if m.Maybe != typed.Maybe_Value {
if m.Maybe != schema.Maybe_Value {
panic("unbox of a maybe rejected")
}
return m.Value
......
......@@ -11,7 +11,7 @@ import (
func (gk generateKindBytes) EmitNodeType(w io.Writer) {
doTemplate(`
var _ ipld.Node = {{ .Type | mungeTypeNodeIdent }}{}
var _ typed.Node = {{ .Type | mungeTypeNodeIdent }}{}
var _ schema.TypedNode = {{ .Type | mungeTypeNodeIdent }}{}
`, w, gk)
}
......
......@@ -51,12 +51,12 @@ func (gk generateKindInt) EmitNativeMaybe(w io.Writer) {
// TODO this can most likely be extracted and DRY'd, just not 100% sure yet
doTemplate(`
type Maybe{{ .Type | mungeTypeNodeIdent }} struct {
Maybe typed.Maybe
Maybe schema.Maybe
Value {{ .Type | mungeTypeNodeIdent }}
}
func (m Maybe{{ .Type | mungeTypeNodeIdent }}) Must() {{ .Type | mungeTypeNodeIdent }} {
if m.Maybe != typed.Maybe_Value {
if m.Maybe != schema.Maybe_Value {
panic("unbox of a maybe rejected")
}
return m.Value
......
......@@ -11,7 +11,7 @@ import (
func (gk generateKindInt) EmitNodeType(w io.Writer) {
doTemplate(`
var _ ipld.Node = {{ .Type | mungeTypeNodeIdent }}{}
var _ typed.Node = {{ .Type | mungeTypeNodeIdent }}{}
var _ schema.TypedNode = {{ .Type | mungeTypeNodeIdent }}{}
`, w, gk)
}
......
......@@ -46,12 +46,12 @@ func (gk generateKindLink) EmitNativeMaybe(w io.Writer) {
// TODO this can most likely be extracted and DRY'd, just not 100% sure yet
doTemplate(`
type Maybe{{ .Type | mungeTypeNodeIdent }} struct {
Maybe typed.Maybe
Maybe schema.Maybe
Value {{ .Type | mungeTypeNodeIdent }}
}
func (m Maybe{{ .Type | mungeTypeNodeIdent }}) Must() {{ .Type | mungeTypeNodeIdent }} {
if m.Maybe != typed.Maybe_Value {
if m.Maybe != schema.Maybe_Value {
panic("unbox of a maybe rejected")
}
return m.Value
......
......@@ -11,7 +11,7 @@ import (
func (gk generateKindLink) EmitNodeType(w io.Writer) {
doTemplate(`
var _ ipld.Node = {{ .Type | mungeTypeNodeIdent }}{}
var _ typed.Node = {{ .Type | mungeTypeNodeIdent }}{}
var _ schema.TypedNode = {{ .Type | mungeTypeNodeIdent }}{}
`, w, gk)
}
......
......@@ -50,12 +50,12 @@ func (gk generateKindList) EmitNativeMaybe(w io.Writer) {
// TODO this can most likely be extracted and DRY'd, just not 100% sure yet
doTemplate(`
type Maybe{{ .Type | mungeTypeNodeIdent }} struct {
Maybe typed.Maybe
Maybe schema.Maybe
Value {{ .Type | mungeTypeNodeIdent }}
}
func (m Maybe{{ .Type | mungeTypeNodeIdent }}) Must() {{ .Type | mungeTypeNodeIdent }} {
if m.Maybe != typed.Maybe_Value {
if m.Maybe != schema.Maybe_Value {
panic("unbox of a maybe rejected")
}
return m.Value
......
......@@ -11,7 +11,7 @@ import (
func (gk generateKindList) EmitNodeType(w io.Writer) {
doTemplate(`
var _ ipld.Node = {{ .Type | mungeTypeNodeIdent }}{}
var _ typed.Node = {{ .Type | mungeTypeNodeIdent }}{}
var _ schema.TypedNode = {{ .Type | mungeTypeNodeIdent }}{}
`, w, gk)
}
......@@ -151,8 +151,8 @@ func (gk generateNbKindList) EmitNodebuilderMethodCreateList(w io.Writer) {
// - This builder, being all about semantics and not at all about serialization,
// is order-insensitive.
// - We don't specially handle being given 'undef' as a value.
// It just falls into the "need a typed.Node" error bucket.
// - We only accept *codegenerated values* -- a typed.Node created
// It just falls into the "need a schema.TypedNode" error bucket.
// - We only accept *codegenerated values* -- a schema.TypedNode created
// in the same schema universe *isn't accepted*.
// REVIEW: We could try to accept those, but it might have perf/sloc costs,
// and it's hard to imagine a user story that gets here.
......@@ -214,9 +214,9 @@ func (gk generateNbKindList) EmitNodebuilderMethodCreateList(w io.Writer) {
panic("type mismatch on struct field assignment: cannot assign null to non-nullable field") // FIXME need an error type for this
}
{{- end}}
tv, ok := v.(typed.Node)
tv, ok := v.(schema.TypedNode)
if !ok {
panic("need typed.Node for insertion into struct") // FIXME need an error type for this
panic("need schema.TypedNode for insertion into struct") // FIXME need an error type for this
}
_, ok = v.({{ .Type.ValueType | mungeTypeNodeIdent }})
if !ok {
......
......@@ -72,12 +72,12 @@ func (gk generateKindString) EmitNativeBuilder(w io.Writer) {
func (gk generateKindString) EmitNativeMaybe(w io.Writer) {
doTemplate(`
type Maybe{{ .Type | mungeTypeNodeIdent }} struct {
Maybe typed.Maybe
Maybe schema.Maybe
Value {{ .Type | mungeTypeNodeIdent }}
}
func (m Maybe{{ .Type | mungeTypeNodeIdent }}) Must() {{ .Type | mungeTypeNodeIdent }} {
if m.Maybe != typed.Maybe_Value {
if m.Maybe != schema.Maybe_Value {
panic("unbox of a maybe rejected")
}
return m.Value
......
......@@ -11,7 +11,7 @@ import (
func (gk generateKindString) EmitNodeType(w io.Writer) {
doTemplate(`
var _ ipld.Node = {{ .Type | mungeTypeNodeIdent }}{}
var _ typed.Node = {{ .Type | mungeTypeNodeIdent }}{}
var _ schema.TypedNode = {{ .Type | mungeTypeNodeIdent }}{}
`, w, gk)
}
......
......@@ -60,11 +60,11 @@ func (gk generateKindStruct) EmitNativeBuilder(w io.Writer) {
{{- if or $field.IsOptional $field.IsNullable }}
{{- /* if both modifiers present, anything goes */ -}}
{{- else if $field.IsOptional }}
if b.{{ $field.Name | titlize }}.Maybe == typed.Maybe_Null {
if b.{{ $field.Name | titlize }}.Maybe == schema.Maybe_Null {
return {{ $field.Type | mungeTypeNodeIdent }}{}, fmt.Errorf("cannot be absent")
}
{{- else if $field.IsNullable }}
if b.{{ $field.Name | titlize }}.Maybe == typed.Maybe_Absent {
if b.{{ $field.Name | titlize }}.Maybe == schema.Maybe_Absent {
return {{ $field.Type | mungeTypeNodeIdent }}{}, fmt.Errorf("cannot be null")
}
{{- end}}
......@@ -88,12 +88,12 @@ func (gk generateKindStruct) EmitNativeBuilder(w io.Writer) {
func (gk generateKindStruct) EmitNativeMaybe(w io.Writer) {
doTemplate(`
type Maybe{{ .Type | mungeTypeNodeIdent }} struct {
Maybe typed.Maybe
Maybe schema.Maybe
Value {{ .Type | mungeTypeNodeIdent }}
}
func (m Maybe{{ .Type | mungeTypeNodeIdent }}) Must() {{ .Type | mungeTypeNodeIdent }} {
if m.Maybe != typed.Maybe_Value {
if m.Maybe != schema.Maybe_Value {
panic("unbox of a maybe rejected")
}
return m.Value
......
......@@ -11,7 +11,7 @@ import (
func (gk generateKindStruct) EmitNodeType(w io.Writer) {
doTemplate(`
var _ ipld.Node = {{ .Type | mungeTypeNodeIdent }}{}
var _ typed.Node = {{ .Type | mungeTypeNodeIdent }}{}
var _ schema.TypedNode = {{ .Type | mungeTypeNodeIdent }}{}
`, w, gk)
}
......@@ -39,12 +39,12 @@ func (gk generateKindStruct) EmitNodeMethodLookupString(w io.Writer) {
{{- range $field := .Type.Fields }}
case "{{ $field.Name }}":
{{- if $field.IsOptional }}
if x.d.{{ $field.Name | titlize }}.Maybe == typed.Maybe_Absent {
if x.d.{{ $field.Name | titlize }}.Maybe == schema.Maybe_Absent {
return ipld.Undef, nil
}
{{- end}}
{{- if $field.IsNullable }}
if x.d.{{ $field.Name | titlize }}.Maybe == typed.Maybe_Null {
if x.d.{{ $field.Name | titlize }}.Maybe == schema.Maybe_Null {
return ipld.Null, nil
}
{{- end}}
......@@ -55,7 +55,7 @@ func (gk generateKindStruct) EmitNodeMethodLookupString(w io.Writer) {
{{- end}}
{{- end}}
default:
return nil, typed.ErrNoSuchField{Type: nil /*TODO*/, FieldName: key}
return nil, schema.ErrNoSuchField{Type: nil /*TODO*/, FieldName: key}
}
}
`, w, gk)
......@@ -95,13 +95,13 @@ func (gk generateKindStruct) EmitNodeMethodMapIterator(w io.Writer) {
case {{ $i }}:
k = String{"{{ $field.Name }}"}
{{- if $field.IsOptional }}
if itr.node.d.{{ $field.Name | titlize }}.Maybe == typed.Maybe_Absent {
if itr.node.d.{{ $field.Name | titlize }}.Maybe == schema.Maybe_Absent {
v = ipld.Undef
break
}
{{- end}}
{{- if $field.IsNullable }}
if itr.node.d.{{ $field.Name | titlize }}.Maybe == typed.Maybe_Null {
if itr.node.d.{{ $field.Name | titlize }}.Maybe == schema.Maybe_Null {
v = ipld.Null
break
}
......@@ -178,8 +178,8 @@ func (gk generateNbKindStruct) EmitNodebuilderMethodCreateMap(w io.Writer) {
// - This builder, being all about semantics and not at all about serialization,
// is order-insensitive.
// - We don't specially handle being given 'undef' as a value.
// It just falls into the "need a typed.Node" error bucket.
// - We only accept *codegenerated values* -- a typed.Node created
// It just falls into the "need a schema.TypedNode" error bucket.
// - We only accept *codegenerated values* -- a schema.TypedNode created
// in the same schema universe *isn't accepted*.
// REVIEW: We could try to accept those, but it might have perf/sloc costs,
// and it's hard to imagine a user story that gets here.
......@@ -195,7 +195,7 @@ func (gk generateNbKindStruct) EmitNodebuilderMethodCreateMap(w io.Writer) {
mb := &{{ .Type | mungeTypeNodeMapBuilderIdent }}{v:&{{ .Type | mungeTypeNodeIdent }}{}}
{{- range $field := .Type.Fields }}
{{- if $field.IsOptional }}
mb.v.d.{{ $field.Name | titlize }}.Maybe = typed.Maybe_Absent
mb.v.d.{{ $field.Name | titlize }}.Maybe = schema.Maybe_Absent
{{- end}}
{{- end}}
return mb, nil
......@@ -221,7 +221,7 @@ func (gk generateNbKindStruct) EmitNodebuilderMethodCreateMap(w io.Writer) {
case "{{ $field.Name }}":
{{- if $field.IsNullable }}
if v.IsNull() {
mb.v.d.{{ $field.Name | titlize}}.Maybe = typed.Maybe_Null
mb.v.d.{{ $field.Name | titlize}}.Maybe = schema.Maybe_Null
{{- if not $field.IsOptional }}
mb.{{ $field.Name }}__isset = true
{{- end}}
......@@ -232,9 +232,9 @@ func (gk generateNbKindStruct) EmitNodebuilderMethodCreateMap(w io.Writer) {
panic("type mismatch on struct field assignment: cannot assign null to non-nullable field") // FIXME need an error type for this
}
{{- end}}
tv, ok := v.(typed.Node)
tv, ok := v.(schema.TypedNode)
if !ok {
panic("need typed.Node for insertion into struct") // FIXME need an error type for this
panic("need schema.TypedNode for insertion into struct") // FIXME need an error type for this
}
x, ok := v.({{ $field.Type | mungeTypeNodeIdent }})
if !ok {
......@@ -247,13 +247,13 @@ func (gk generateNbKindStruct) EmitNodebuilderMethodCreateMap(w io.Writer) {
mb.v.d.{{ $field.Name | titlize}} = x
{{- end}}
{{- if $field.IsOptional }}
mb.v.d.{{ $field.Name | titlize}}.Maybe = typed.Maybe_Value
mb.v.d.{{ $field.Name | titlize}}.Maybe = schema.Maybe_Value
{{- else}}
mb.{{ $field.Name }}__isset = true
{{- end}}
{{- end}}
default:
return typed.ErrNoSuchField{Type: nil /*TODO:typelit*/, FieldName: ks}
return schema.ErrNoSuchField{Type: nil /*TODO:typelit*/, FieldName: ks}
}
return nil
}
......@@ -283,7 +283,7 @@ func (gk generateNbKindStruct) EmitNodebuilderMethodCreateMap(w io.Writer) {
return {{ $field.Type | mungeNodebuilderConstructorIdent }}()
{{- end}}
default:
panic(typed.ErrNoSuchField{Type: nil /*TODO:typelit*/, FieldName: ks})
panic(schema.ErrNoSuchField{Type: nil /*TODO:typelit*/, FieldName: ks})
}
return nil
}
......
......@@ -50,12 +50,12 @@ func (gk generateStructReprMapNode) EmitNodeMethodLookupString(w io.Writer) {
{{- range $field := .Type.Fields }}
case "{{ $field | $type.RepresentationStrategy.GetFieldKey }}":
{{- if $field.IsOptional }}
if rn.n.d.{{ $field.Name | titlize}}.Maybe == typed.Maybe_Absent {
if rn.n.d.{{ $field.Name | titlize}}.Maybe == schema.Maybe_Absent {
return ipld.Undef, ipld.ErrNotExists{ipld.PathSegmentOfString(key)}
}
{{- end}}
{{- if $field.IsNullable }}
if rn.n.d.{{ $field.Name | titlize}}.Maybe == typed.Maybe_Null {
if rn.n.d.{{ $field.Name | titlize}}.Maybe == schema.Maybe_Null {
return ipld.Null, nil
}
{{- end}}
......@@ -66,7 +66,7 @@ func (gk generateStructReprMapNode) EmitNodeMethodLookupString(w io.Writer) {
{{- end}}
{{- end}}
default:
return nil, typed.ErrNoSuchField{Type: nil /*TODO*/, FieldName: key}
return nil, schema.ErrNoSuchField{Type: nil /*TODO*/, FieldName: key}
}
}
`, w, gk)
......@@ -110,13 +110,13 @@ func (gk generateStructReprMapNode) EmitNodeMethodMapIterator(w io.Writer) {
case {{ $i }}:
k = String{"{{ $field | $type.RepresentationStrategy.GetFieldKey }}"}
{{- if $field.IsOptional }}
if itr.node.d.{{ $field.Name | titlize}}.Maybe == typed.Maybe_Absent {
if itr.node.d.{{ $field.Name | titlize}}.Maybe == schema.Maybe_Absent {
itr.idx++
continue
}
{{- end}}
{{- if $field.IsNullable }}
if itr.node.d.{{ $field.Name | titlize}}.Maybe == typed.Maybe_Null {
if itr.node.d.{{ $field.Name | titlize}}.Maybe == schema.Maybe_Null {
v = ipld.Null
break
}
......@@ -150,7 +150,7 @@ func (gk generateStructReprMapNode) EmitNodeMethodLength(w io.Writer) {
l := {{ len .Type.Fields }}
{{- range $field := .Type.Fields }}
{{- if $field.IsOptional }}
if rn.n.d.{{ $field.Name | titlize}}.Maybe == typed.Maybe_Absent {
if rn.n.d.{{ $field.Name | titlize}}.Maybe == schema.Maybe_Absent {
l--
}
{{- end}}
......@@ -220,7 +220,7 @@ func (gk generateStructReprMapNb) EmitNodebuilderMethodCreateMap(w io.Writer) {
mb := &{{ .Type | mungeTypeReprNodeMapBuilderIdent }}{v:&{{ .Type | mungeTypeNodeIdent }}{}}
{{- range $field := .Type.Fields }}
{{- if $field.IsOptional }}
mb.v.d.{{ $field.Name | titlize }}.Maybe = typed.Maybe_Absent
mb.v.d.{{ $field.Name | titlize }}.Maybe = schema.Maybe_Absent
{{- end}}
{{- end}}
return mb, nil
......@@ -247,7 +247,7 @@ func (gk generateStructReprMapNb) EmitNodebuilderMethodCreateMap(w io.Writer) {
}
{{- if $field.IsNullable }}
if v.IsNull() {
mb.v.d.{{ $field.Name | titlize}}.Maybe = typed.Maybe_Null
mb.v.d.{{ $field.Name | titlize}}.Maybe = schema.Maybe_Null
mb.{{ $field.Name }}__isset = true
return nil
}
......@@ -256,9 +256,9 @@ func (gk generateStructReprMapNb) EmitNodebuilderMethodCreateMap(w io.Writer) {
panic("type mismatch on struct field assignment: cannot assign null to non-nullable field") // FIXME need an error type for this
}
{{- end}}
tv, ok := v.(typed.Node)
tv, ok := v.(schema.TypedNode)
if !ok {
panic("need typed.Node for insertion into struct") // FIXME need an error type for this
panic("need schema.TypedNode for insertion into struct") // FIXME need an error type for this
}
x, ok := v.({{ $field.Type | mungeTypeNodeIdent }})
if !ok {
......@@ -271,12 +271,12 @@ func (gk generateStructReprMapNb) EmitNodebuilderMethodCreateMap(w io.Writer) {
mb.v.d.{{ $field.Name | titlize}} = x
{{- end}}
{{- if $field.IsOptional }}
mb.v.d.{{ $field.Name | titlize}}.Maybe = typed.Maybe_Value
mb.v.d.{{ $field.Name | titlize}}.Maybe = schema.Maybe_Value
{{- end}}
mb.{{ $field.Name }}__isset = true
{{- end}}
default:
return typed.ErrNoSuchField{Type: nil /*TODO:typelit*/, FieldName: ks}
return schema.ErrNoSuchField{Type: nil /*TODO:typelit*/, FieldName: ks}
}
return nil
}
......@@ -307,7 +307,7 @@ func (gk generateStructReprMapNb) EmitNodebuilderMethodCreateMap(w io.Writer) {
return {{ $field.Type | mungeNodebuilderConstructorIdent }}()
{{- end}}
default:
panic(typed.ErrNoSuchField{Type: nil /*TODO:typelit*/, FieldName: ks})
panic(schema.ErrNoSuchField{Type: nil /*TODO:typelit*/, FieldName: ks})
}
return nil
}
......
package typed
package schema
type Maybe uint8
......
// The `schema/tests` package contains behavioral tests for type-constrained
// Node implementations -- meant to work with either codegenerated Nodes OR
// with the runtime typed.Node wrappers, checking for the same behavior on each.
// with the runtime schema.TypedNode wrappers, checking for the same behavior on each.
package tests
......@@ -59,13 +59,13 @@ type Type interface {
//
// Note that a schema.Kind is a different enum than ipld.ReprKind;
// and furthermore, there's no strict relationship between them.
// typed.Node values can be described by *two* distinct ReprKinds:
// schema.TypedNode values can be described by *two* distinct ReprKinds:
// one which describes how the Node itself will act,
// and another which describes how the Node presents for serialization.
// For some combinations of Type and representation strategy, one or both
// of the ReprKinds can be determined statically; but not always:
// it can sometimes be necessary to inspect the value quite concretely
// (e.g., `typed.Node{}.Representation().ReprKind()`) in order to find
// (e.g., `schema.TypedNode{}.Representation().ReprKind()`) in order to find
// out exactly how a node will be serialized! This is because some types
// can vary in representation kind based on their value (specifically,
// kinded-representation unions have this property).
......
package typed
package schema
import (
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/schema"
)
// typed.Node is a superset of the ipld.Node interface, and has additional behaviors.
// schema.TypedNode is a superset of the ipld.Node interface, and has additional behaviors.
//
// A typed.Node can be inspected for its schema.Type and schema.Kind,
// A schema.TypedNode can be inspected for its schema.Type and schema.Kind,
// which conveys much more and richer information than the Data Model layer
// ipld.ReprKind.
//
// There are many different implementations of typed.Node.
// There are many different implementations of schema.TypedNode.
// One implementation can wrap any other existing ipld.Node (i.e., it's zero-copy)
// and promises that it has *already* been validated to match the typesystem.Type;
// another implementation similarly wraps any other existing ipld.Node, but
// defers to the typesystem validation checking to fields that are accessed;
// and when using code generation tools, all of the generated native Golang
// types produced by the codegen will each individually implement typed.Node.
//
// Note that typed.Node can wrap *other* typed.Node instances.
// Imagine you have two parts of a very large code base which have codegen'd
// components which are from different versions of a schema. Smooth migrations
// and zero-copy type-safe data sharing between them: We can accommodate that!
// types produced by the codegen will each individually implement schema.TypedNode.
//
// Typed nodes sometimes have slightly different behaviors than plain nodes:
// For example, when looking up fields on a typed node that's a struct,
// the error returned for a lookup with a key that's not a field name will
// be ErrNoSuchField (instead of ErrNotExists).
// These behaviors apply to the typed.Node only and not their representations;
// These behaviors apply to the schema.TypedNode only and not their representations;
// continuing the example, the .Representation().LookupString() method on
// that same node for the same key as plain `.LookupString()` will still
// return ErrNotExists, because the representation isn't a typed.Node!
type Node interface {
// typed.Node acts just like a regular Node for almost all purposes;
// which ReprKind it acts as is determined by the TypeKind.
// return ErrNotExists, because the representation isn't a schema.TypedNode!
type TypedNode interface {
// schema.TypedNode acts just like a regular Node for almost all purposes;
// which ipld.ReprKind it acts as is determined by the TypeKind.
// (Note that the representation strategy of the type does *not* affect
// the ReprKind of typed.Node -- rather, the representation strategy
// the ReprKind of schema.TypedNode -- rather, the representation strategy
// affects the `.Representation().ReprKind()`.)
//
// For example: if the `.Type().Kind()` of this node is "struct",
......@@ -45,7 +39,7 @@ type Node interface {
ipld.Node
// Type returns a reference to the reified schema.Type value.
Type() schema.Type
Type() Type
// Representation returns an ipld.Node which sees the data in this node
// in its representation form.
......@@ -56,11 +50,3 @@ type Node interface {
// if the streatgy is "tuple", then it will be ReprKind=="list".
Representation() ipld.Node
}
// unboxing is... ugh, we probably should codegen an unbox method per concrete type.
// (or, attach them to the non-pointer type, which would namespace in an alloc-free way, but i don't know if that's anything but confusing.)
// there are notes about this from way back at 2019.01; reread to see if any remain relevant and valid.
// main important point is: it's not gonna be casting.
// if casting was sufficient to unbox, it'd mean every method on the Node interface would be difficult to use as a field name on a struct type. undesirable.
// okay, or, alternative, we flip this to `superapi.Footype{}.Fields().FrobFieldName()`. that strikes me as unlikely to be pleasing, though.
// istm we can safely expect direct use of field names much, much more often that flipping back and forth to hypergeneric node; so we should optimize syntax for that accordingly.
......@@ -26,7 +26,7 @@ package schema
---
We may also need to consider distinct reification paths: we may want one
that returns a new node tree which is eagerly converted to typed.Node
that returns a new node tree which is eagerly converted to schema.TypedNode
recursively; and another that returns a lazyNode which wraps things
with their typed node constraints only as they're requested.
(Note that the latter would have interesting implications for any code
......@@ -37,8 +37,8 @@ package schema
A further fun issue which needs consideration: well, I'll just save a snip
of prospective docs I wrote while trying to iterate on these functions:
// Note that using Validate on a node that's already a typed.Node is likely
// to be nonsensical. In many schemas, the typed.Node tree is actually a
// Note that using Validate on a node that's already a schema.TypedNode is likely
// to be nonsensical. In many schemas, the schema.TypedNode tree is actually a
// different depth than its representational tree (e.g. unions can cause this),
... and that's ... that's a fairly sizable issue that needs resolving.
......@@ -62,7 +62,7 @@ package schema
---
And finally: both "Validate" and "Reify" methods might actually belong
in the typed.Node package -- if they make *any* reference to `typed.Node`,
in the schema.TypedNode package -- if they make *any* reference to `schema.TypedNode`,
then they have no choice (otherwise, cyclic imports would occur).
If we make a "Validate" that works purely on the schema.Type info, and
returns *only* errors: only then we can have it in the schema package.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment