Integrate BACKBEAT SDK and resolve KACHING license validation

Major integrations and fixes:
- Added BACKBEAT SDK integration for P2P operation timing
- Implemented beat-aware status tracking for distributed operations
- Added Docker secrets support for secure license management
- Resolved KACHING license validation via HTTPS/TLS
- Updated docker-compose configuration for clean stack deployment
- Disabled rollback policies to prevent deployment failures
- Added license credential storage (CHORUS-DEV-MULTI-001)

Technical improvements:
- BACKBEAT P2P operation tracking with phase management
- Enhanced configuration system with file-based secrets
- Improved error handling for license validation
- Clean separation of KACHING and CHORUS deployment stacks

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-09-06 07:56:26 +10:00
parent 543ab216f9
commit 9bdcbe0447
4730 changed files with 1480093 additions and 1916 deletions

1
vendor/github.com/ipld/go-ipld-prime/.gitattributes generated vendored Normal file
View File

@@ -0,0 +1 @@
**/testdata/fuzz/** binary

3
vendor/github.com/ipld/go-ipld-prime/.gitmodules generated vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule ".ipld"]
path = .ipld
url = https://github.com/ipld/ipld/

763
vendor/github.com/ipld/go-ipld-prime/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,763 @@
CHANGELOG
=========
Here is collected some brief notes on major changes over time, sorted by tag in which they are first available.
Of course for the "detailed changelog", you can always check the commit log! But hopefully this summary _helps_.
Note about version numbering: All release tags are in the "v0.${x}" range. _We do not expect to make a v1 release._
Nonetheless, this should not be taken as a statement that the library isn't _usable_ already.
Much of this code is used in other libraries and products, and we do take some care about making changes.
(If you're ever wondering about stability of a feature, ask -- or contribute more tests ;))
- [Planned/Upcoming Changes](#planned-upcoming-changes)
- [Released Changes Log](#released-changes)
Planned/Upcoming Changes
------------------------
Here are some outlines of changes we intend to make that affect the public API:
- **IPLD Amend**: is likely to land soon; it implements a more efficient underlying architecture to support IPLD Patch and related features. IPLD Amend adds an interface to allow incremental changes to `Node`s in an efficient way. Whereas IPLD Patch is a protocol for expressing changes. We're still working on figuring out exactly where it fits in the stack and making sure it won't be disruptive but early benchmarks are very promising for both Patch and traversal-based transforms. See https://github.com/ipld/go-ipld-prime/pull/445 for more.
- **Layered `Node` implementation optimizations**: When layering different implementations of `Node` builders or consumers, having to defer through basicnode types can lead to large inefficiencies of memory and speed. We are looking at ways to improve this situation, including ways to *assemble* layered assemblers. See https://github.com/ipld/go-ipld-prime/issues/443 for discussion and some initial plans.
- **Selectors**: There have been some recurring wishes to do something about the Selector package layout. There's no intended or prioritized date for this. See https://github.com/ipld/go-ipld-prime/issues/236 for more.
- **Absent / "Not found" values**: There may be some upcoming changes to exactly how "not found" values are handled in order to clarify and standardize the subject. There's no finalized date for this. See https://github.com/ipld/go-ipld-prime/issues/360 for more.
Released Changes
----------------
### v0.20.0
go-ipld-prime's release policy says that:
> even numbers should be easy upgrades; odd numbers may change things
As such, v0.20.0 is a relatively minor release with a grab-bag of small improvements and fixes.
_2023 February 11_
Schema errors can now [`errors.Is`](https://pkg.go.dev/errors#Is):
* \[[`61c9ab10d4`](https://github.com/ipld/go-ipld-prime/commit/61c9ab10d4)] - **feat**: support errors.Is for schema errors (Ian Davis) [#476](https://github.com/ipld/go-ipld-prime/pull/476)
Schema DMT (schema/dmt) is now more usable from the outside and has a new `ConcatenateSchemas` function that can be used to combine two schemas into one:
* \[[`db9d8a7512`](https://github.com/ipld/go-ipld-prime/commit/db9d8a7512)] - Export schema/dmt.TypeSystem. (Eric Myhre) [#483](https://github.com/ipld/go-ipld-prime/pull/483)
* \[[`39818c169a`](https://github.com/ipld/go-ipld-prime/commit/39818c169a)] - Add a SchemaConcatenate operation. (Eric Myhre) [#483](https://github.com/ipld/go-ipld-prime/pull/483)
* \[[`c68ba53c67`](https://github.com/ipld/go-ipld-prime/commit/c68ba53c67)] - More accurate name for structure that contains easy access to prototypes. (Eric Myhre) [#483](https://github.com/ipld/go-ipld-prime/pull/483)
* \[[`2ecabf1217`](https://github.com/ipld/go-ipld-prime/commit/2ecabf1217)] - Add several pieces of docs to schema/dmt. (Eric Myhre)
* \[[`33475f0448`](https://github.com/ipld/go-ipld-prime/commit/33475f0448)] - Fix mispatched package declaration. (Eric Myhre)
The DAG-CBOR codec now has an `DontParseBeyondEnd` option (default `false`) that allows it to parse undelimited streamed objects. This matches the same functionality already in DAG-JSON and should only be used for specialised cases:
* \[[`7b00b1490f`](https://github.com/ipld/go-ipld-prime/commit/7b00b1490f)] - feat(dagcbor): mode to allow parsing undelimited streamed objects (Rod Vagg) [#490](https://github.com/ipld/go-ipld-prime/pull/490)
`datamodel.Copy` got some direct test coverage and will now complain if you try to copy a `nil` node:
* \[[`f4bb2daa27`](https://github.com/ipld/go-ipld-prime/commit/f4bb2daa27)] - fix(datamodel): add tests to Copy, make it complain on nil (Rod Vagg) [#491](https://github.com/ipld/go-ipld-prime/pull/491)
The LinkSystem data loading check will compare links (CIDs) to ensure it loaded what you wanted; this now properly supports the case where your link is a pointer:
* \[[`1fc56b8e7a`](https://github.com/ipld/go-ipld-prime/commit/1fc56b8e7a)] - Fix hash mismatch error on matching link pointer (Masih H. Derkani) [#480](https://github.com/ipld/go-ipld-prime/pull/480)
### v0.19.0
_2022 October 13_
go-ipld-prime's release policy says that:
> even numbers should be easy upgrades; odd numbers may change things
The major change in this release is a bump to Go 1.18.
#### 🛠 Breaking Changes
Update go.mod to Go 1.18.
#### 🔦 Highlights
* **Codecs**: [Correct JSON codec Bytes handling](https://github.com/ipld/go-ipld-prime/pull/472). This change does not impact DAG-JSON, which is the generally recommended codec for JSON output as the JSON codec cannot properly handle Bytes or Links.
* **Dependencies**:
* Update to go-multihash@v0.2.1: https://github.com/multiformats/go-multihash/releases/tag/v0.2.1
* Update to go-multicodec@v0.6.0: https://github.com/multiformats/go-multicodec/releases/tag/v0.6.0
* Update to go-cid@v0.3.2: https://github.com/ipfs/go-cid/compare/v0.2.0...v0.3.2
### v0.18.0
_2022 August 01_
go-ipld-prime's release policy says that:
> even numbers should be easy upgrades; odd numbers may change things
So, as an even number, this v0.18.0 release should be a smooth ride for upgraders from v0.17.0. We have 3 major feature additions, all focused on [Bindnode](https://pkg.go.dev/github.com/ipld/go-ipld-prime/node/bindnode).
#### 🔦 Highlights
* **Bindnode**: [Custom Go type converters](https://github.com/ipld/go-ipld-prime/pull/414) - Bindnode performs bidirectional mapping of Go types to the IPLD Data Model, and in doing so, it assumes a straightforward mapping of values to their encoded forms. But there are common cases where a Go type doesn't have a straightforward path to serialization, either because the encoded form needs a custom layout, or because bindnode doesn't have enough information to infer a serialization pattern. Custom Go type converters for bindnode allow a user to supply a pair of converter functions for a Go type that dictate how to map that type to an IPLD Data Model kind. See the **[bindnode documentation](https://pkg.go.dev/github.com/ipld/go-ipld-prime/node/bindnode)** for more information.
* **Bindnode**: [Type registry](https://github.com/ipld/go-ipld-prime/pull/437) - Setting up Go type mappings with Bindnode involves some boilerplate. A basic type registry is now available that takes some of this boilerplate away; giving you a single place to register, and perform conversions to and from Go types, Data Model (`Node`) forms or directly through serialization. See the **[bindnode/registry documentation](https://pkg.go.dev/github.com/ipld/go-ipld-prime/node/bindnode/registry)** for more information.
* **Bindnode** [Full `uint64` support](https://github.com/ipld/go-ipld-prime/pull/414/commits/87211682cb963ef1c98fa63909f67a8b02d1108c) - the `uint64` support introduced in go-ipld-prime@v0.17.0 has been wired into Bindnode. The Data Model (`Node`) forms expose integers as `int64` values, which is lossy for unsigned 64-bit integers. Bindnode Go types using `uint64` values are now lossless in round-trips through serialization to codecs that support the full range (DAG-CBOR most notably).
You can see all of these new features in action using Filecoin Go types, allowing a mapping between Go types, Data Model (`Node`) forms, and their DAG-CBOR serialized forms with [data-transfer vouchers](https://github.com/filecoin-project/go-fil-markets/pull/713). These features also allow us to interact with the original Go types, without modification, including `big.Int` serialization to `Bytes`, Filecoin `Signature` serialization to a byte-prefix discriminated `Bytes` and more. Since the Go types are unchanged, they can also simultaneously support [cbor-gen](https://github.com/whyrusleeping/cbor-gen) serialization, allowing an easier migration path.
### v0.17.0
_2022 Jun 15_
go-ipld-prime's release policy says that:
> even numbers should be easy upgrades; odd numbers may change things
In that spirit, this v0.17.0 release includes some potentially breaking changes. Although minor, they are marked below and they may lead to behavioral changes in your use of this library.
#### 🛠 Breaking Changes
* **Codecs**:
* DAG-CBOR, DAG-JSON: [Error on `cid.Undef` links in dag{json,cbor} encoding](https://github.com/ipld/go-ipld-prime/pull/433) - previously, encoding Link nodes that were empty CIDs (uninitialized zero-value or explicitly `cid.Undef`) would have passed through the DAG-CBOR or DAG-JSON codecs, silently producing erroneous output that wouldn't successfully pass back through a decode. (Rod Vagg)
* **Bindnode**:
* [Panic early if API has been passed ptr-to-ptr](https://github.com/ipld/go-ipld-prime/pull/427) - previous usage of bindnode using pointers-to-pointers may have deferred (or in some cases avoided) panics until deeper usage of the API, this change makes it earlier to make it clear that pointer-to-pointer is not appropriate usage. (Rod Vagg)
* **Build**:
* [Drop Go 1.16.x testing & begin testing Go 1.18.x](https://github.com/ipld/go-ipld-prime/pull/394) (Daniel Martí)
* Note also that in this release, the [github.com/ipfs/**go-cid**](https://github.com/ipfs/go-cid) dependency is upgraded from 0.0.4 to 0.2.0 which includes a breaking change with the removal of the `cid.Codecs` and `cid.CodecToStr` maps which may disruptive. See [the go-cid@0.2.0 release page for details](https://github.com/ipfs/go-cid/releases/tag/v0.2.0).
#### 🔦 Highlights
* **Data Model**:
* [Introduce `UIntNode` interface, used within DAG-CBOR codec to quietly support full uint64 range](https://github.com/ipld/go-ipld-prime/pull/413) (Rod Vagg)
* **Bindnode**:
* Fuzzing and hardening for production use (Daniel Martí)
* Refuse to decode empty union values (Daniel Martí)
* [Allow nilable types for IPLD `optional`/`nullable`](https://github.com/ipld/go-ipld-prime/pull/401) (Daniel Martí)
* [More helpful error message for common enum value footgun](https://github.com/ipld/go-ipld-prime/pull/430) (Rod Vagg)
* [Infer links and `Any` from Go types](https://github.com/ipld/go-ipld-prime/pull/432) (Rod Vagg)
* **Schemas**:
* DMT: Proper checking for unknown union members (Daniel Martí)
* DMT: Enum representations must be valid members (Daniel Martí)
* DMT: Reject duplicate or missing union representation members (Daniel Martí)
* DSL: [Support `stringjoin` struct representation and `stringprefix` union representation](https://github.com/ipld/go-ipld-prime/pull/397) (Eric Evenchick)
* DMT, DSL: [Enable inline types](https://github.com/ipld/go-ipld-prime/pull/404) (Rod Vagg)
* **Patch**:
* [Add initial version of IPLD Patch feature](https://github.com/ipld/go-ipld-prime/pull/350) (Eric Myhre) *(helped across the line by mauve and Rod Vagg)*
* **Codecs**:
* DAG-CBOR: [Reject extraneous content after valid (complete) CBOR object](https://github.com/ipld/go-ipld-prime/pull/386) (Rod Vagg)
* DAG-CBOR: [add `DecodeOptions.ExperimentalDeterminism`](https://github.com/ipld/go-ipld-prime/pull/390) (currently only checking map sorting order) (Daniel Martí)
* Printer: [Fix printing of floats](https://github.com/ipld/go-ipld-prime/pull/412) (Dustin Long)
* DAG-JSON: [Add option to not parse beyond end of structure](https://github.com/ipld/go-ipld-prime/pull/435) (Petar Maymounkov)
* **Build**:
* Fix [macOS](https://github.com/ipld/go-ipld-prime/pull/400) and [Windows](https://github.com/ipld/go-ipld-prime/pull/405) testing (Rod Vagg)
* [Fix 32-bit build support](https://github.com/ipld/go-ipld-prime/pull/407) (Rod Vagg)
* [Make staticcheck and govet happy across codebase](https://github.com/ipld/go-ipld-prime/pull/406) (Rod Vagg)
* Enable full [unified-ci](https://github.com/protocol/.github) GitHub Actions suite, including auto-updating (Rod Vagg)
* [Enable dependabot, with monthly checks](https://github.com/ipld/go-ipld-prime/pull/417) (and update all dependencies) (Rod Vagg)
Special thanks to **Daniel Martí** for many bindnode improvements and hardening, fuzzing across the library and improvements to the Schema DMT and DSL.
### v0.16.0
_2022 March 09_
- New: `traversal.WalkTransforming` is finally implemented! (It's been a stub for quite a while.) This works similarly to the other transform features, but can do more than change to the structure during a single walk.
- New: Selectors support partial/ranged match on bytes or strings nodes. (This is also a new feature for Selectors, recently specified.)
[[#375](https://github.com/ipld/go-ipld-prime/pull/375); seealso specs in [ipld#184](https://github.com/ipld/ipld/pull/184)]
- New: there's a `datamodel.LargeBytesNode` interface, which makes it possible to handle "large" blobs of bytes as a `Node`, without necessarily forcing them all into memory at once. (This is optional; you add the methods to match the interface if your Node implementation supports the feature.)
[[#372](https://github.com/ipld/go-ipld-prime/pull/372)]
- Slightly more specifically: this interface is `Node` plus a method that returns an `io.ReadSeeker`. (Pretty standard golang I/O and byte slice management concepts should carry you from there in the usual ways.)
- This is a **really big deal** -- for example, this means that an [ADL](https://ipld.io/docs/advanced-data-layouts/) can support reading of arbitrarily large bytes without an issue. (Hello, transparently readable large sharded blobs!)
- New: there's a "resume" (or, skipahead) mechanism for traversals and selectors. Engage it by simply setting the `traversal.Config.StartAtPath` field.
[[#358](https://github.com/ipld/go-ipld-prime/pull/358)]
- New: `dagcbor` now has a `EncodedLength(Node) int` function, which can calculate the expected serial message length without actually encoding. (The usefulness of this may be situational, but it's there if you want it.)
- Improved: `bindnode`, yet again, in more ways that can easily be summarized.
- Better support for pointers in more places in your golang types.
- Many panics either fixed or routed into calmer errors.
- Unsigned intergers are now supported in your golang types.
- Some fixes for AssignNode working correctly (e.g. at the type or representation level, as appropriate; sometimes previously it would use the type level incorrectly).
- Various fixes to handling absent fields correctly.
- A `datamodel.Node` can now be used for an `any` field.
- Fixed: selectors now behave correctly for a recursion clause that just contains a recursion edge immedately. (It's still not a sensible selector, really, but it's valid.) Previously this would panic, which was nasty.
- Fixed: `bindnode` now correctly doesn't include absent fields in the count of length when looking at the representation-level view of structs.
- Improved: all our batteries-included codecs double check while encoding that the number iterator steps over a map matches its self-reported length. (This doesn't matter in many cases, but does defend you a little better against a `Node` implementation with a bug, if you happen to be so unlucky.)
- Improved: miscellaneous performance work in the `schema/*` area.
Thank you to @mvdan, @warpfork, @hannahhoward, @rvagg, @willscott, @arajasek and others
for all their work that went into making this release (as well as all the point releases in v0.14.x leading up to it) happen.
Finally, please note that we're starting to try out some new (and slightly more formal) governance and review and merge processes.
Check out https://github.com/ipld/go-ipld-prime/issues/370 for more information.
The aim is to make things generally more inclusive and involve more contributors!
This is still experimental and may be subject to change, but if you'd like to have better expectations about who can review and what the process should be like, we hope this will be a step in a helpful direction.
(Feedback about this experiment welcome!)
### v0.14.x
(There were releases `v0.14.1`, `v0.14.2`, `v0.14.3`, and `v0.14.4` -- but all were in rapid succession, very minor, and hitting the same areas; we'll keep the notes brief and condensed.)
- New: Selectors can include clauses for signalling the use of ADLs!
[[#301](https://github.com/ipld/go-ipld-prime/pull/301); seealso specs in [ipld#149](https://github.com/ipld/ipld/pull/149)+[ipld#170](https://github.com/ipld/ipld/pull/170)]
- Also kindly note that there are expected to be many ways of signalling ADL invocations -- this is only one of them.
See the IPLD website for more on this topic as a whole: https://ipld.io/docs/advanced-data-layouts/signalling/
- Improved: `bindnode`, in ways more various than can easily be summarized.
- The `cidlink.Link` type can be bound to links in the data.
- Enums are now supported.
- The `any` typekind is now supported.
- Improved: both the `schema/dmt` and `schema/dsl` packages (and in some cases, the `schema` package itself) continue to be improved and become more complete.
- Structs with tuple representation are now supported.
- Enums with int representation are now supported.
- The `any` typekind is now supported.
- Changed: the dag-json codec will tolerate padded base64 in bytes content upon read. It does so silently. (It is not still possible to emit this kind of serial data with this library; it is noncanonical.)
[[#309](https://github.com/ipld/go-ipld-prime/pull/309)]
- Changed: the cbor and dag-cbor codec will now tolerate CBOR's "undef" token. It will coerce it to a null token when reading. Previously, encountering the undef token would result in a parse error. (It is still not possible to emit this token with this library.)
[[#308](https://github.com/ipld/go-ipld-prime/pull/308)]
- New: the `traversal` package gained a `WalkLocal` function. This simply does a walk that does not cross any links.
### v0.14.0
_2021 November 11_
This release is a smooth-sailing release, and mostly contains new features, quality-of-life improvements,
and some significant improvements to the completeness and usability of features that have been in development across previous releases.
There shouldn't be a lot of surprises, and upgrading should be easy.
Some of the biggest improvements include: `bindnode` now supports most IPLD features and is increasingly stable;
the `schema` system now has functioning `schema/dmt` and `schema/dsl` packages, and can parse schema documents smoothly(!);
if you haven't seen the `printer` package that first quietly appeared in `v0.12.2`, you should definitely check it out now;
and we have some new `storage` APIs that might be worth checking out, too.
There are also many, many other smaller improvements.
See the complete list and further deatils below
(and don't forget to check out the notes under the other `v0.12.*` headings, if you haven't absorbed those updates already, too!):
- New: `datamodel.Copy`: a helper function to do a shallow copy from one node to another.
- You don't often need this, because nodes are supposed to be immutable!
But it still sometimes comes in handy, for example, if you want to change the memory layout you're using by moving data into a different node implementation.
- Improved: documentation of APIs. (Especially, for subtler bits like `NodeAssembler.AssignNode`.)
- New: `datamodel.Link` now requires a `Binary()` function. In contrast to `Link.String()` (which is supposed to return something printable), `Link.Binary()` should give you the rawest thing possible. (It's equivalent to `go-cid.CID.KeyString`.)
- New: **a new storage API**, including one **batteries-included** filesystem storage implementation, and **adapters** to several other different storage APIs. [[#265](https://github.com/ipld/go-ipld-prime/pull/265), [#279](https://github.com/ipld/go-ipld-prime/pull/279)]
- The primary goal of this is the "batteries included" part: using the new `storage/fsstore` package, you should now be able to make simple applications with IPLD and use a simple sharded disk storage system (it'll look vaguely like a `.git/objects` directory), and do it in about five minutes and without pulling in any additional complex dependencies.
- If you want to develop new storage systems or make adapters to them: the APIs in `storage` package are designed to be implemented easily.
- The `storage` APIs are designed entirely around types found in the golang standard library. You do not need to import anything in the `storage` package in order to implement its interfaces!
- The minimal APIs that a storage system has to implement are _very_ small. Two functions. Every additional feature, or optimization that you can offer: those all have their own interfaces, and we use feature-detection on them. You can implement as much or as little as you like.
- As a user of the storage APIs: use the functions in the `storage` package. Those functions take a storage system as a parameter, and will do feature detection _for you_.
- This means you can always write your code to call the APIs you _want_, and the `storage` functions will figure out how to map it onto the storage system that you _have_ (whatever it supports) in the most efficient way it can.
- As a user of the `LinkSystem` API: you can ignore most of this! If you want to use the new `storage` APIs, there are setup methods on `LinkSystem` that will take them as a parameter. If you have existing code wired up with the previous APIs, it still works too.
- As someone who already has code and wonders how to migrate:
- If you're using the `linking.Storage*Opener` API: you don't have to do anything. Those still work too.
- If you were using code from other repos like `ipfs/go-ipfs-blockstore` or `ipfs/go-datastore` or so on: those have adapters now in the `storage/*adapter` packages! You should now be able to use those more easily, with less custom glue code. (There's also now a migration readme in the repo root: check that out.)
- If you would like to ask: "is it fast?" -- yes. You'll find that the new `storage/fsstore`, our batteries-included filesystem storage system, is comparable (or beating) the `go-ds-flatfs` package that you may have been using in the past. (More benchmarks and any performance improvement patches will of course be welcome -- but at the very least, there's no reason to hold back on using the new system.)
- New: `LinkSystem` has some new methods: `LoadRaw` and `LoadPlusRaw` give you the ability to get data model nodes loaded, and _also_ receive the raw binary blobs.
- This can be useful if you're building an application that's piping data around to other serial APIs without necessarily transforming it. (No need to reserialize if that's your journey.)
- New: a CLI tool has begun development!
- ... and almost immediately been removed again, to live in its own repo: check out https://github.com/ipld/go-ipldtool .
- Improved: many more things about `bindnode`.
- `bindnode` now understands `go-cid.CID` fields.
- Kinded unions are much more completely supported.
- Many TODO panics have gone away, replaced by finished features.
- `bindnode` will increasingly check that the golang types you give it can be structurally matched to the schema if you provide one, which gives better errors earlier, and increases the ease and safety of use drastically.
- Improved: the `schema/dmt` and `schema/dsl` packages are increasingly complete.
- There are also now helper functions in the root package which will do the whole journey of "load a file, parse the Schema DSL, compile and typecheck the DMT, and give you the type info in handy golang interfaces", all at once! Check out `ipld.LoadSchema`!
- New: there is a codegen feature for `bindnode` which will produce very terse golang structs matching a schema and ready to be bound back to `bindnode`!
- This competes with the older `gengo` code generator -- by comparison, the `bindnode` code generator produces much, _much_ less code. (However, be advised that the performance characteristics are probably also markedly different; and we do not have sufficient benchmarks to comment on this at this time.)
- Internal: many tests are being ported to `quicktest`. There should be no external impact to this, but we look forward to removing some of the other test libraries from our dependency tree in the near future.
- Improved: `printer` now supports links and bytes!
- Improved: `printer` is now more resilient and works even on relatively misbehaved `Node` implementations, such as those which implement `schema.TypedNode` but then rudely and nonsensically return nil type info. (We don't expect all code to be resilient against misbehaved `Node` implementations... but for a debug tool in particular? It's good to have it handle as much as it can.)
This, and the last few releases tagged in the `v0.12.*` series, include invaluable contributions from
@mvdan, @warpfork, @rvagg, @willscott, @masih, @hannahhoward, @aschmahmann, @ribasushi,
and probably yet more others who have contributed through code and design reviews,
or by using these libraries and demanding they continue to become better.
Thanks to each and every one of the people who carry this project forward!
### v0.12.3
_2021 September 30_
(This is a minor release; we'll keep the notes brief.)
- Fixed: using `SkipMe` in a traversal now skips only that subtree of nodes, not the remainder of the block!
[[#251](https://github.com/ipld/go-ipld-prime/pull/251)]
- New: `traversal` features now have budgets! You can set a "budget" value, and watch it monotonically decrement as your operations procede. This makes it easy to put limits on the amount of work you'll do.
[[#260](https://github.com/ipld/go-ipld-prime/pull/260)]
- New: `traversal` features can be configured to visit links they encounter only once (and ignore them if seen again).
[[#252](https://github.com/ipld/go-ipld-prime/pull/252)]
- Note that this is not without caveats: this is not merely an optimization; enabling it _may_ produce logically different outcomes, depending on what your selector is.
This is because links are ignored when seen again, even if they're seen for a different _reason_, via a different path, etc.
- Fixed: a very nasty off-by-one in unions produced by the "gogen" codegen.
[[#257](https://github.com/ipld/go-ipld-prime/pull/257)]
- Improved: the test suites for typed nodes now provide much better coverage (to prevent something like the above from happening again, even in other implementations).
- New: `schema/dsl`! This package contains parsers for the IPLD Schema DSL, and produces data structures in `schema/dmt` form.
- Removed: other misc partially-complete packages. (This will surely bother no one; it's just cleanup.)
- Removed: `codec/jst`. If you were using that, [`jst` has its own repo](https://github.com/warpfork/go-jst/) now.
- Improved: `traversal` now uses the error wrapping ("`%w`") feature in more places.
- Changed: `printer` keeps empty maps and lists and strings on a single line.
- Changed: `schema.TypeName` is now just an alias of `string`. This may result in somewhat less casting; or, you might not notice it.
- Improved: the `schema/dmt` package continues to be improved and become more complete.
- Some changes also track fixes in the schema spec, upstream. (Or caused those fixes!)
- New/Improved: the `schema` package describes several more things which it always should have. Enums, for example.
### v0.12.2
_2021 September 8_
(This is a minor release; we'll keep the notes brief.)
- New: the `printer` package has appeared, and aims to provide an information-rich, debug-readable, human-friendly output of data from an IPLD node tree. [[#238](https://github.com/ipld/go-ipld-prime/pull/238/)]
- This works for both plain data model data, and for typed data, and annotates type information if present.
- Note that this is _not_ a codec: it's specifically _richer_ than that. Conversely, this printer format is not designed to be parsed back to data model data. Use a codec for a codec's job; use the printer for debugging and inspection jobs.
- Fixed/Improved: more things about the `bindnode` system. (It's still early and improving fast.)
- Fixed: json codec, cbor codec, and their dag variants all now return ErrUnexpectedEOF in the conditions you'd expect. (Previously they sometimes just returned EOF, which could be surprising.)
- Changed/Improved: the `schema/dmt` package is now implemented using `bindnode`, and there's a more complete `Compile()` feature. (This is still very early, in this tag. More to come here soon.)
### v0.12.1
_2021 August 30_
(This is a minor release; we'll keep the notes brief.)
- Fixed/Improved: many things about the `bindnode` system. (It's still early and improving fast.)
- Changed: the strings for `schema.TypeKind_*` are lowercase. (The docs and specs all act this way, and always have; it was a strange error for this code to have titlecase.)
- New: the root package contains more helper methods for encoding and decoding operations
### v0.12.0
_2021 August 19_
This release is a momentous one. It contains a sizable refactor:
we've extracted some of the most key interfaces to a new package, called `datamodel`!
It's also an even numbered release tag, which we generally use to indicate "upgrading should be smooth sailing".
Surprisingly, despite the magnitude of the refactor, we mean that, too.
Golang's "alias" feature has been used _heavily_ for this change process,
and downstream code that worked on the previous release should continue to work on this release too, without syntactic changes.
Why did we do this?
The root package, `ipld`, is now going to be a place where we can put helpful functions.
Synthesis functions that put all the pieces of IPLD together for you.
The functions you're probably looking for; the high-level stuff that gets work done.
Previously, the root package was _guts_: the lowest level interfaces, the more core stuff...
which was cool to see (arguably), but tended not to be the things you'd want to see _first_ as a new user.
And because everything _else_ in the world depended on those interface,
we could never put interesting high-level functions in the same package
(or if we tried, compilation would fail, because of import cycles)...
which meant any time we wanted to add helper functions for getting useful work done,
we'd be stuck cramming them off into subpackages somewhere.
While this worked, the discoverability for a new user was terribly arduous.
We hope this pivot to how we organize the code helps you find your way through IPLD!
We haven't yet added many of the new helper features to the updated root package.
Those will come in the very near future.
(Follow along with commits on the master branch if you want to try the new APIs early!)
This release is being made just to cover the refactor, before we steam along any further.
Your existing code should continue working without changes because the root `ipld` package
still contains all the same types -- just as aliases.
You can choose to update your code to use the types where they've moved to
(which is mostly the `datamodel` package), or, if you prefer... just leave it as-is.
Some aliases may be removed over time; if so, they'll be marked with a comment to that effect,
and there should be plenty of warning and time to change.
In some cases, continuing to use the `ipld` package directly will remain acceptable indefinitely.
The new intention is that common work should often be possible to do only by
importing the `ipld` package, and users should only need to dive into
the more specific subpackages if they been to need direct access to more detailed APIs
for performance or other reasons.
That's it for the big refactor news.
There's also some sweet new features in bindnode,
and a few other important fixes to recently introduced features.
In detail:
- Changed: that massive refactor, described above. Gosh it's big.
[[#228](https://github.com/ipld/go-ipld-prime/pull/228)]
- New: the selectors system is tested against the language-agnostic selector specs, from the IPLD specs+docs repo!
[[#231](https://github.com/ipld/go-ipld-prime/pull/231)]
- This uses a new fixture format, called [testmark](https://github.com/warpfork/go-testmark#what-is-the-testmark-format), which is managed by a library called [go-testmark](https://pkg.go.dev/github.com/warpfork/go-testmark).
- The fixtures are drawn in by a git submodule. The actual fixture content remains in the [ipld/ipld](https://github.com/ipld/ipld/) repo.
- These new tests will be run if you have cloned the git submodule (and of course, by CI). If you do not clone the submodule that contains the fixtures, the tests will quietly skip.
- We hope this will be a template for how to do more testing in the future, while keeping it closely coordinated with specs, and in sync with other implementations of IPLD in other languages!
- Improved: bindnode: in a variety of ways.
[[#226](https://github.com/ipld/go-ipld-prime/pull/226)]
- Several error messages are improved.
- Kinded unions support complex recipients even for string kinds. (E.g., putting a struct with stringjoin representation inside a kinded union now works correctly.)
- Stringprefix unions now work even with no explicit delimiter.
- Please note that bindnode is, and remains, considered experimental. While we're improving it, it's still something to use at your own risk.
- Changed/Improved: bindnode: unions are now handled completely differently (and much better).
[[#223](https://github.com/ipld/go-ipld-prime/pull/223)]
- In short: now they expect a golang struct which has a field for each of the possible members, and each of them should be a pointer. This is type safe and works reasonably idiomatically in golang.
- This is a fairly huge improvement, because it fixes the "bindnode unions force downshift into anonymous types" problem, which was tracked as [issue#210](https://github.com/ipld/go-ipld-prime/issues/210).
- Fixed: the selector `ExploreRecursive.stopAt` feature now actually... works. It was completely broken when it was introduced in the last release. (Tests. They're important.)
[[#229](https://github.com/ipld/go-ipld-prime/pull/229)]
- Notice how we've also now got selector tests driven by fixtures appearing in this release. Hopefully that decreases the odds of something like this happening again.
### v0.11.0
_2021 August 12_
This release is an odd numbered release, which means it may contain breaking changes.
Unfortunately, the changes here may be particularly, tricky, as well -- for the most part, they're not compile-time detectable.
They're behavioral changes. Much more subtle. Run tests on your systems before accepting these changes.
Specifically: several codecs now enforce sorting when emitting serial data.
There's also some details of what's changing that makes it milder than it first sounds:
most of the changes are around codecs becoming *more* spec-compliant.
So, for example, if you were using another IPLD library that always enforced sorting on e.g. DAG-CBOR,
you won't be surprised or experience it much like a "change" when using this version of go-ipld-prime, which now also enforces such sorting in that codec.
Also! At least one huge and awesome new feature: `bindnode`.
This is a new implementation of `ipld.Node` which can bind to native golang structures using reflection,
which provides a new and easy-to-use way to move data in and out of golang structures (or traverse them, etc!) with IPLD interfaces and codecs.
See the full change list for details:
- New: some new helpful constructors for making Selectors out of serial forms can now be found in the `traversal/selector/parse` package.
[[#199](https://github.com/ipld/go-ipld-prime/pull/199)]
- Some constants are also included which show some examples of creating common selectors from JSON.
- Fixed: cbor, dag-cbor, json, and dag-json codecs now all accept parsing a block that contains just a null token alone. (Previously, this returned an "unexpected EOF" error, which was silly.)
[[#217](https://github.com/ipld/go-ipld-prime/pull/217)]
- Fixed (upstream): json floats are actually supported. (You might've had this already, if anything dragged in a newer version of the `refmt` library. We just make sure to require this ourselves in our `go.mod` file now.)
[[#215](https://github.com/ipld/go-ipld-prime/pull/215)]
- New: Selectors now support some kinds of conditions. Specifically, `ExploreRecursive` clauses can contain a `stopAt` condition, and the condition system now supports `Condition_IsLink`, which can be used to do an equality check for CIDs.
[[#214](https://github.com/ipld/go-ipld-prime/pull/214)]
- Fixed: in codegen'd types, the `LinkTargetNodePrototype` on links was returning the wrong prototype; now it returns the right one.
[[#211](https://github.com/ipld/go-ipld-prime/pull/211)]
- New: `schema.TypedPrototype` interface, which is like `ipld.NodePrototype` but also has methods for asking `Type() schema.Type` and `Representation() ipld.NodePrototype`, both of which should probably instantly make sense to you.
[[#195](https://github.com/ipld/go-ipld-prime/pull/195)]
- Changed: the dag-json and dag-cbor codecs now apply sorting.
[[#203](https://github.com/ipld/go-ipld-prime/pull/203), [#204](https://github.com/ipld/go-ipld-prime/pull/204)]
- This means all serial data created with these codecs is sorted as advised by their respective specifications.
Previously, the implementations of these codecs was order-preserving, and emitted data in whatever order the `ipld.Node` yielded it.
- There may be new performance costs originating from this sorting.
- The codecs do not reject other orderings when parsing serial data.
The `ipld.Node` trees resulting from deserialization will still preserve the serialized order.
However, it has now become impossible to re-encode data in that same preserved order.
- If doing your own encoding, there are customization options in `dagcbor.EncodeOptions.MapSortMode` and `dagjson.EncodeOptions.MapSortMode`.
(However, note that these options are not available to you while using any systems that only operate in terms of multicodec codes.)
- _Be cautious of this change._ It is now extremely easy to write code which puts data into an `ipld.Node` in memory in one order,
then save and load that data using these codecs, and end up with different data as a result because the sorting changes the order of data.
For some applications, this may not be a problem; for others, it may be surprising.
In particular, mind this carefully in the presense of other order-sensitive logic -- for example,
such as when using Selectors, whose behaviors also depend on ordering of data returned when iterating over an `ipld.Node`.
- Fixed/Changed: the dag-json codec no longer emits whitespace (!). It is now spec-compliant.
[[#202](https://github.com/ipld/go-ipld-prime/pull/202)]
- This means hashes of content produced by dag-json codec will change. This is unfortunate, but the previous implementation was woefully and wildly out of sync with the spec, and addressing that is a predominating concern.
- Removed: `fluent/quip` has been dropped. `fluent/qp` is superior. `fluent/quip` was too easy to use incorrectly, so we no longer offer it.
[[#197](https://github.com/ipld/go-ipld-prime/pull/197)]
- This was an experimental package introduced a few releases ago, together with caveats that we may choose to drop it. The warning was purposeful!
We don't believe that this will be too painful of a change; not many things depended on the `fluent/quip` variant, and those that did should not be difficult to rewrite to `fluent/qp`.
- New: `node/basic.Chooser` is a function that implements `traversal.LinkTargetNodePrototypeChooser`. It's a small handy quality-of-life increase if you need to supply such a function, which is common.
[[#198](https://github.com/ipld/go-ipld-prime/pull/198)]
- New: `bindnode`! **This is a huge feature.** The beginnings of it may have been visible in v0.10.0, but it's grown into a usable thing we're ready to talk about.
- Bindnode lets you write golang types and structures, and "bind" them into being IPLD Nodes and supporting Data Model operations by using golang reflection.
- The result of working with `bindnode` is somewhere between using basicnode and using codegen:
it's going to provide some structural constraints (like codegen) and provide moderate performance (it lets you use structs rather than memory-expensive maps; but reflection is still going to be slower than codegen).
- However, most importantly, `bindnode` is *nice to use*. It doesn't have a huge barrier to entry like codegen does.
- `bindnode` can be used with _or without_ IPLD Schemas. For basic golang types, a schema can be inferred automatically. For more advanced features (e.g. any representation customization), you can provide a Schema.
- Please note that though it is now usable, bindnode remains _in development_. There is not yet any promise that it will be frozen against changes.
- In fact, several changes are expected; in particular, be advised there is some sizable change expected around the shape of golang types expected for unions.
- Improved: tests for behavior of schema typed nodes are now extracted to a package, where they are reusable.
- The same tests now cover the `bindnode` implementation, as well as being used in tests of our codegen outputs.
- Previously, these tests were already mostly agnostic of implementation, but had been thrown into packages in a way that made them hard to reuse.
- Improved (or Fixed, depending on your point of view): dag-json codec now supports bytes as per the spec.
[[#166](https://github.com/ipld/go-ipld-prime/pull/166),[#216](https://github.com/ipld/go-ipld-prime/pull/216)]
- Bytes are encoded in roughly this form: `{"/":{"bytes":"base64data"}}`.
- Note: the json codec does _not_ include this behavior; this is behavior specific to dag-json.
### v0.10.0
_2021 June 02_
v0.10.0 is a mild release, containing _no_ breaking changes, but lots of cool new stuff. Update at your earliest convenience.
There's a bunch of cool new features in here, some of which are significant power-ups for the ecosystem (e.g. the `NodeReifier` API), so we recommend updating as soon as possible.
There's also some sizable performance improvements available for generated code, so go forth and update your generated code too!
Check out the full feature list:
- New: an `ipld.DeepEqual` method lets you easily compare two `ipld.Node` for equality. (This is useful in case you have nodes with two different internal implementations, different memory layouts, etc, such that native golang equality would not be semantically correct.)
[[#174](https://github.com/ipld/go-ipld-prime/pull/174)]
- New: the multicodec package exposes a `multicodec.Registry` type, and also some `multicodec.List*` methods.
[[#172](https://github.com/ipld/go-ipld-prime/pull/172), [#176](https://github.com/ipld/go-ipld-prime/pull/176)]
- Please be cautious of using these `List*` methods. It's very possible to create race conditions with these, especially if using them on the global default registry instance.
If we detect that these access methods seem to produce a source of bugs and design errors in downstream usage, they will be removed.
Consider doing whatever you're doing by buildling your own registry systems, and attaching whatever semantics your system desires to those systems, rather than shoehorning this intentionally limited system into doing things it isn't made to do.
- Improved: the dag-json codec now actually supports bytes!
(Perhaps surprisingly, this was a relatively recent addition to the dag-json spec. We've now caught up with it.)
[[#166](https://github.com/ipld/go-ipld-prime/pull/166)]
- Improved: the codegen system now gofmt's the generated code immediately. You no longer need to do this manually in a separate step.
[[#163](https://github.com/ipld/go-ipld-prime/pull/163)]
- Improved: the codegen system is slightly faster at emitting code (due to use of more buffering during writes).
[[#161](https://github.com/ipld/go-ipld-prime/pull/161)]
- Improved: the codegen system will now avoid pointers in the generated "Maybe" types, if they're known to be small in memory (and thus, reasonable to inline).
[[#160](https://github.com/ipld/go-ipld-prime/pull/160)]
- This is quite likely to result in performance improvements for most programs, as it decreases the number of small memory allocations done, and amount of time spent on dereferencing, cache misses, etc.
Some workloads demonstrated over 10% speed increases, and 40% decreases in allocation counts.
(Of course, run your own benchmarks; not all workloads are equal.)
- New: `ipld.LinkSystem` now contains a "reification" hook system. **This is really cool.**
- The center of this is the `ipld.LinkSystem.NodeReifier` field, and the `ipld.NodeReifier` function type.
- The `ipld.NodeReifier` function type is simply `func(LinkContext, Node, *LinkSystem) (Node, error)`.
- The purpose and intention of this is: you can use this hooking point in order to decide where to engage advanced IPLD features like [ADLs](https://ipld.io/glossary/#adl).
One can use a `NodeReifier` to decide what ADLs to use and when... even when in the middle of a traversal.
- For example: one could write a NodeReifier that says "when I'm in a path that ends with '`foosys/*/hamt`', i'm going to try to load that as if it's a HAMT ADL".
With that hook in place, you'd then be able to walks over whole forests of data with `traversal.*` functions, and they would automatically load the relevant ADL for you transparently every time that pattern is encountered, without disrupting or complicating the walk.
- In the future, we might begin to offer more structural and declaratively configurable approaches to this, and eventually, attempt to standardize them.
For now: you can build any solution you like using this hook system. (And we'll probably plug in any future declarative systems via these same hooks, too.)
- All this appeared in [#158](https://github.com/ipld/go-ipld-prime/pull/158).
- New: `ipld.LinkSystem` now contains a boolean flag for `TrustedStorage`. If set to true, it will cause methods like `Load` to _skip hashing_ when loading content. **_Do not do this unless you know what you're doing._**
[[#149](https://github.com/ipld/go-ipld-prime/pull/149)]
- New: a json (as opposed to dag-json) codec is now available from this repo. It does roughly what you'd expect. (It's like dag-json, but explicitly rejects encoding links and bytes, and correspondingly does not have dag-json's special decoding behaviors that produce those kinds.)
[[#152](https://github.com/ipld/go-ipld-prime/pull/152)]
- New: a cbor (as opposed to dag-cbor) codec is now available from this repo. Same story as the json codec: it just explicitly doesn't support links (because you should use dag-cbor if you want that).
[[#153](https://github.com/ipld/go-ipld-prime/pull/153)]
This contained a ton of contributions from lots of people: especially thanks to @mvdan, @hannahhoward, and @willscott for invaluable contributions.
### v0.9.0
_2021 March 15_
v0.9.0 is a pretty significant release, including several neat new convenience features, but most noticeably, significantly reworking how linking works.
Almost any code that deals with storing and linking data will need some adaptation to handle this release.
We're sorry about the effort this may require, but it should be worth it.
The new LinkSystem API should let us introduce a lot more convenience features in the future, and do so *without* pushing additional breakage out to downstream users; this is an investment in the future.
The bullet points below contain all the fun details.
Note that a v0.8.0 release version has been skipped.
We use odd numbers to indicate the existence of significant changes;
and while usually we try to tag an even-number release between each odd number release so that migrations can be smoothed out,
in this case there simply weren't enough interesting points in between to be worth doing so.
- Change: linking has been significantly reworked, and now primarily works through the `ipld.LinkSystem` type.
- This is cool, because it makes a lot of things less circuitous. Previously, working with links was a complicated combination of Loader and Storer functions, the Link interface contained the Load method, it was just... complicated to figure out where to start. Now, the answer is simple and constant: "Start with LinkSystem". Clearer to use; clearer to document; and also coincidentally a lot clearer to develop for, internally.
- The PR can be found here: https://github.com/ipld/go-ipld-prime/pull/143
- `Link.Load` -> `LinkSystem.Load` (or, new: `LinkSystem.Fill`, which lets you control memory allocation more explicitly).
- `LinkBuilder.Build` -> `LinkSystem.Store`.
- `LinkSystem.ComputeLink` is a new feature that produces a Link without needing to store the data anywhere.
- The `ipld.Loader` function is now most analogous to `ipld.BlockReadOpener`. You now put it into use by assigning it to a `LinkLoader`'s `StorageReadOpener` field.
- The `ipld.Storer` function is now most analogous to `ipld.BlockWriteOpener`. You now put it into use by assigning it to a `LinkLoader`'s `StorageWriteOpener` field.
- 99% of the time, you'll probably start with `linking/cid.DefaultLinkSystem()`. You can assign to fields of this to customize it further, but it'll get you started with multihashes and multicodecs and all the behavior you expect when working with CIDs.
- (So, no -- the `cidlink` package hasn't gone anywhere. Hopefully it's a bit less obtrusive now, but it's still here.)
- The `traversal` package's `Config` struct now uses a `LinkSystem` instead of a `Loader` and `Storer` pair, as you would now probably expect.
- If you had code that was also previously passing around `Loader` and `Storer`, it's likely a similar pattern of change will be the right direction for that code.
- In the _future_, further improvements will come from this: we're now much, much closer to making a bunch of transitive dependencies become optional (especially, various hashers, which currently, whenever you pull in the `linking/cid` package, come due to `go-cid`, and are quite large). When these improvements land (again, they're not in this release), you'll need to update your applications to import hashers you need if they're not in the golang standard library. For now: there's no change.
- Change: multicodec registration is now in the `go-ipld-prime/multicodec` package.
- Previously, this registry was in the `linking/cid` package. These things are now better decoupled.
- This will require packages which register codecs to make some very small updates: e.g. `s/cidlink.RegisterMulticodecDecoder/multicodec.RegisterDecoder/`, and correspondingly, update the package imports at the top of the file.
- New: some pre-made storage options (e.g. satisfying the `ipld.StorageReadOpener` and `ipld.StorageWriteOpener` function interfaces) have appeared! Find these in the `go-ipld-prime/storage` package.
- Currently this only includes a simple in-memory storage option. This may be useful for testing and examples, but probably not much else :)
- These are mostly intended to be illustrative. You should still expect to find better storage mechanisms in other repos.
- Change: some function names in codec packages are ever-so-slightly updated. (They're verbs now, instead of nouns, which makes sense because they're functions. I have no idea what I was thinking with the previous naming. Sorry.)
- `s/dagjson.Decoder/dagjson.Decode/g`
- `s/dagjson.Decoder/dagjson.Encode/g`
- `s/dagcbor.Decoder/dagcbor.Decode/g`
- `s/dagcbor.Encoder/dagcbor.Encode/g`
- If you've only been using these indirectly, via their multicodec indicators, you won't have to update anything at all to account for this change.
- New: several new forms of helpers to make it syntactically easy to create new IPLD data trees with golang code!
- Check out the `go-ipld-prime/fluent/quip` package! See https://github.com/ipld/go-ipld-prime/pull/134, where it was introduced, for more details.
- Check out the `go-ipld-prime/fluent/qp` package! See https://github.com/ipld/go-ipld-prime/pull/138, where it was introduced, for more details.
- Both of these offer variations on `fluent` which have much lower costs to use. (`fluent` incurs allocations during operation, which has a noticable impact on performance if used in a "hot" code path. Neither of these two new solutions do!)
- For now, both `quip` and `qp` will be maintained. They have similar goals, but different syntaxes. If one is shown drastically more popular over time, we might begin to consider deprecating one in favor of the other, but we'll need lots of data before considering that.
- We won't be removing the `fluent` package anytime soon, but we probably wouldn't recommend building new stuff on it. `qp` and `quip` are both drastically preferable for performance reasons.
- New: there is now an interface called `ipld.ADL` which can be used for a certain kind of feature detection.
- This is an experimental new concept and likely subject to change.
- The one key trait we've found all ADLs tend to share right now is that they have a "synthesized" view and "substrate" view of their data. So: the `ipld.ADL` interface states that a thing is an `ipld.Node` (for the synthesized view), and from it you should be able to access a `Substrate() ipld.Node`, and that's about it.
### v0.7.0
_2020 December 31_
v0.7.0 is a small release that makes a couple of breaking changes since v0.6.0.
However, the good news is: they're all very small changes, and we've kept them in a tiny group,
so if you're already on v0.6.0, this update should be easy.
And we've got scripts to help you.
There's also one cool new feature: `traversal.FocusedTransform` is now available to help you make mutations to large documents conveniently.
- Change: all interfaces and APIs now use golang `int64` rather than golang `int`. [#125](https://github.com/ipld/go-ipld-prime/pull/125)
- This is necessary because the IPLD Data Model specifies that integers must be "at least 2^53" in range, and so since go-ipld-prime may also be used on 32-bit architectures, it is necessary that we not use the `int` type, which would fail to be Data Model-compliant on those architectures.
- The following GNU sed lines should assist this transition in your code, although some other changes that are more difficult automate may also be necessary:
```
sed -ri 's/(func.* AsInt.*)\<int\>/\1int64/g' **/*.go
sed -ri 's/(func.* AssignInt.*)\<int\>/\1int64/g' **/*.go
sed -ri 's/(func.* Length.*)\<int\>/\1int64/g' **/*.go
sed -ri 's/(func.* LookupByIndex.*)\<int\>/\1int64/g' **/*.go
sed -ri 's/(func.* Next.*)\<int\>/\1int64/g' **/*.go
sed -ri 's/(func.* ValuePrototype.*)\<int\>/\1int64/g' **/*.go
```
- Change: we've renamed the types talking about "kinds" for greater clarity. `ipld.ReprKind` is now just `ipld.Kind`; `schema.Kind` is now `schema.TypeKind`. We expect to use "kind" and "typekind" consistently in prose and documentation from now on, as well. [#127](https://github.com/ipld/go-ipld-prime/pull/127)
- Pretty much everyone who's used this library has said "ReprKind" didn't really make sense as a type name, so, uh, yeah. You were all correct. It's fixed now.
- "kind" now always means "IPLD Data Model kind", and "typekind" now always means "the kinds which an IPLD Schema type can have".
- You can find more examples of how we expect to use this in a sentence from now on in the discussion that lead to the rename: https://github.com/ipld/go-ipld-prime/issues/94#issuecomment-745307919
- The following GNU sed lines should assist this transition in your code:
```
sed -ri 's/\<Kind\(\)/TypeKind()/g' **/*.go
sed -ri 's/\<Kind_/TypeKind_/g' **/*.go
sed -i 's/\<Kind\>/TypeKind/g' **/*.go
sed -i 's/ReprKind/Kind/g' **/*.go
```
- Feature: `traversal.FocusedTransform` works now! :tada: You can use this to take a node, say what path inside it you want to update, and then give it an updated value. Super handy. [#130](https://github.com/ipld/go-ipld-prime/pull/130)
### v0.6.0
_2020 December 14_
v0.6.0 is a feature-packed release and has a few bugfixes, and _no_ significant breaking changes. Update at your earliest convenience.
Most of the features have to do with codegen, which we now consider to be in **alpha** -- go ahead and use it! (We're starting to self-host some things in it, so any changes will definitely be managed from here on out.)
A few other small handy helper APIs have appeared as well; see the detailed notes for those.
Like with the last couple of releases, our intent is to follow this smooth-sailing change with another release shortly which will include some minor but noticable API changes, and that release may require you to make some code changes.
Therefore, we suggest upgrading to this one first, beacuse it's an easy waypoint before the next change.
- Feature: codegen is a reasonably usable alpha! We now encourage trying it out (but still only for those willing to experience an "alpha" level of friction -- UX still rough, and we know it).
- Consult the feature table in the codegen package readme: many major features of IPLD Schemas are now supported.
- Structs with tuple representations? Yes.
- Keyed unions? Yes.
- Structs with stringjoin representations? Yes. Including nested? _Yes_.
- Lots of powerful stuff is now available to use.
- See [the feature table in the codegen readme](https://github.com/ipld/go-ipld-prime/blob/v0.6.0/schema/gen/go/README.md#completeness) for details.
- Many generated types now have more methods for accessing them in typed ways (in addition to the usual `ipld.Node` interfaces, which can access the same data, but lose explicit typing). [#106](https://github.com/ipld/go-ipld-prime/pull/106)
- Maps and lists now have both lookup methods and iterators which know the type of the child keys and values explicitly.
- Cool: when generating unions, you can choose between different implementation strategies (favoring either interfaces, or embedded values) by using Adjunct Config. This lets you tune for either speed (reduced allocation count) or memory footprint (less allocation size, but more granular allocations).
- See notes in [#60](https://github.com/ipld/go-ipld-prime/pull/60) for more detail on this. We'll be aiming to make configurability of this more approachable and better documented in future releases, as we move towards codegen tools usable as CLI tools.
- Cyclic references in types are now supported.
- ... mostly. Some manual configuration may sometimes be required to make sure the generated structure wouldn't have an infinite memory size. We'll keep working on making this smoother in the future.
- Field symbol overrides now work properly. (E.g., if you have a schema with a field called "type", you can make that work now. Just needs a field symbol override in the Adjunct Config when doing codegen!)
- Codegen'd link types now implemented the `schema.TypedLinkNode` interface where applicable.
- Structs now actually validate all required fields are present before allowing themselves to finish building. Ditto for their map representations.
- Much more testing. And we've got a nice new declarative testcase system that makes it easier to write descriptions of how data should behave (at both the typed and representation view levels), and then just call one function to run exhaustive tests to make sure it looks the same from every inspectable API.
- Change: codegen now outputs a fixed set of files. (Previously, it output one file per type in your schema.) This makes codegen much more managable; if you remove a type from your schema, you don't have to chase down the orphaned file. It's also just plain less clutter to look at on the filesystem.
- Demo: as proof of the kind of work that can be done now with codegen, we've implemented the IPLD Schema schema -- the schema that describes IPLD Schema declarations -- using codegen. It's pretty neat.
- Future: we'll be replacing most of the current current `schema` package with code based on this generated stuff. Not there yet, though. Taking this slow.
- You can see the drafts of this, along with new features based on it, in [#107](https://github.com/ipld/go-ipld-prime/pull/107).
- Feature: the `schema` typesystem info packages are improved.
- Cyclic references in types are now supported.
- (Mind that there are still some caveats about this when fed to codegen, though.)
- Graph completeness is now validated (e.g. missing type references emit useful errors)!
- Feature: there's a `traversal.Get` function. It's like `traversal.Focus`, but just returns the reached data instead of dragging you through a callback. Handy.
- Feature/bugfix: the DAG-CBOR codec now includes resource budgeting limits. This means it's a lot harder for a badly-formed (or maliciously formed!) message to cause you to run out of memory while processing it. [#85](https://github.com/ipld/go-ipld-prime/pull/85)
- Bugfix: several other panics from the DAG-CBOR codec on malformed data are now nice politely-returned errors, as they should be.
- Bugfix: in codegen, there was a parity break between the AssembleEntry method and AssembleKey+AssembleValue in generated struct NodeAssemblers. This has been fixed.
- Minor: ErrNoSuchField now uses PathSegment instead of a string. You probably won't notice (but this was important interally: we need it so we're able to describe structs with tuple representations).
- Bugfix: an error path during CID creation is no longer incorrectly dropped. (I don't think anyone ever ran into this; it only handled situations where the CID parameters were in some way invalid. But anyway, it's fixed now.)
- Performance: when `cidlink.Link.Load` is used, it will do feature detection on its `io.Reader`, and if it looks like an already-in-memory buffer, take shortcuts that do bulk operations. I've heard this can reduce memory pressure and allocation counts nicely in applications where that's a common scenario.
- Feature: there's now a `fluent.Reflect` convenience method. Its job is to take some common golang structs like maps and slices of primitives, and flip them into an IPLD Node tree. [#81](https://github.com/ipld/go-ipld-prime/pull/81)
- This isn't very high-performance, so we don't really recommend using it in production code (certainly not in any hot paths where performance matters)... but it's dang convenient sometimes.
- Feature: there's now a `traversal.SelectLinks` convenience method. Its job is to walk a node tree and return a list of all the link nodes. [#110](https://github.com/ipld/go-ipld-prime/pull/110)
- This is both convenient, and faster than doing the same thing using general-purpose Selectors (we implemented it as a special case).
- Demo: you can now find a "rot13" ADL in the `adl/rot13adl` package. This might be useful reference material if you're interested in writing an ADL and wondering what that entails. [#98](https://github.com/ipld/go-ipld-prime/pull/98)
- In progress: we've started working on some new library features for working with data as streams of "tokens". You can find some of this in the new `codec/codectools` package.
- Functions are available for taking a stream of tokens and feeding them into a NodeAssembler; and for taking a Node and reading it out as a stream of tokens.
- The main goal in mind for this is to provide reusable components to make it easier to implement new codecs. But maybe there will be other uses for this feature too!
- These APIs are brand new and are _extremely subject to change_, much more so than any other packages in this repo. If you work with them at this stage, _do_ expect to need to update your code when things shift.
### v0.5.0
_2020 July 2_
v0.5.0 is a small release -- it just contains a bunch of renames.
There are _no_ semantic changes bundled with this (it's _just_ renames) so this should be easy to absorb.
- Renamed: `NodeStyle` -> `NodePrototype`.
- Reason: it seems to fit better! See https://github.com/ipld/go-ipld-prime/issues/54 for a full discussion.
- This should be a "sed refactor" -- the change is purely naming, not semantics, so it should be easy to update your code for.
- This also affects some package-scoped vars named `Style`; they're accordingly also renamed to `Prototype`.
- This also affects several methods such as `KeyStyle` and `ValueStyle`; they're accordingly also renamed to `KeyPrototype` and `ValuePrototype`.
- Renamed: `(Node).Lookup{Foo}` -> `(Node).LookupBy{Foo}`.
- Reason: The former phrasing makes it sound like the "{Foo}" component of the name describes what it returns, but in fact what it describes is the type of the param (which is necessary, since Golang lacks function overloading parametric polymorphism). Adding the preposition should make this less likely to mislead (even though it does make the method name moderately longer).
- This should be a "sed refactor" -- the change is purely naming, not semantics, so it should be easy to update your code for.
- Renamed: `(Node).Lookup` -> `(Node).LookupNode`.
- Reason: The shortest and least-qualified name, 'Lookup', should be reserved for the best-typed variant of the method, which is only present on codegenerated types (and not present on the Node interface at all, due to Golang's limited polymorphism).
- This should be a "sed refactor" -- the change is purely naming, not semantics, so it should be easy to update your code for. (The change itself in the library was fairly literally `s/Lookup(/LookupNode(/g`, and then `s/"Lookup"/"LookupNode"/g` to catch a few error message strings, so consumers shouldn't have it much harder.)
- Note: combined with the above rename, this method overall becomes `(Node).LookupByNode`.
- Renamed: `ipld.Undef` -> `ipld.Absent`, and `(Node).IsUndefined` -> `(Node).IsAbsent`.
- Reason: "absent" has emerged as a much, much better description of what this value means. "Undefined" sounds nebulous and carries less meaning. In long-form prose docs written recently, "absent" consistently fits the sentence flow much better. Let's just adopt "absent" consistently and do away with "undefined".
- This should be a "sed refactor" -- the change is purely naming, not semantics, so it should be easy to update your code for.
### v0.4.0
v0.4.0 contains some misceleanous features and documentation improvements -- perhaps most notably, codegen is re-introduced and more featureful than previous rounds -- but otherwise isn't too shocking.
This tag mostly exists as a nice stopping point before the next version coming up (which is planned to include several API changes).
- Docs: several new example functions should now appear in the godoc for how to use the linking APIs.
- Feature: codegen is back! Use it if you dare.
- Generated code is now up to date with the present versions of the core interfaces (e.g., it's updated for the NodeAssembler world).
- We've got a nice big feature table in the codegen package readme now! Consult that to see which features of IPLD Schemas now have codegen support.
- There are now several implemented and working (and robustly tested) examples of codegen for various representation strategies for the same types. (For example, struct-with-stringjoin-representation.) Neat!
- This edition of codegen uses some neat tricks to not just maintain immutability contracts, but even prevent the creation of zero-value objects which could potentially be used to evade validation phases on objects that have validation rules. (This is a bit experimental; we'll see how it goes.)
- There are oodles and oodles of deep documentation of architecture design choices recorded in "HACKME_*" documents in the codegen package that you may enjoy if you want to contribute or understand why generated things are the way they are.
- Testing infrastructure for codegen is now solid. Running tests for the codegen package will: exercise the generation itself; AND make sure the generated code compiles; AND run behavioral tests against it: the whole gamut, all from regular `go test`.
- The "node/gendemo" package contains a real example of codegen output... and it's connected to the same tests and benchmarks as other node implementations. (Are the gen'd types fast? yes. yes they are.)
- There's still lots more to go: interacting with the codegen system still requires writing code to interact with as a library, as we aren't shipping a CLI frontend to it yet; and many other features are still in development as well. But you're welcome to take it for a spin if you're eager!
- Feature: introduce JSON Tables Codec ("JST"), in the `codec/jst` package. This is a codec that emits bog-standard JSON, but leaning in on the non-semantic whitespace to produce aligned output, table-like, for pleasant human reading. (If you've used `column -t` before in the shell: it's like that.)
- This package may be a temporary guest in this repo; it will probably migrate to its own repo soon. (It's a nice exercise of our core interfaces, though, so it incubated here.)
- I'm quietly shifting the versioning up to the 0.x range. (Honestly, I thought it was already there, heh.) That makes this this "v0.4".
### v0.0.3
v0.0.3 contained a massive rewrite which pivoted us to using NodeAssembler patterns.
Code predating this version will need significant updates to match; but, the performance improvements that result should be more than worth it.
- Constructing new nodes has a major pivot towards using "NodeAssembler" pattern: https://github.com/ipld/go-ipld-prime/pull/49
- This was a massively breaking change: it pivoted from bottom-up composition to top-down assembly: allocating large chunks of structured memory up front and filling them in, rather than stitching together trees over fragmented heap memory with lots of pointers
- "NodeStyle" and "NodeBuilder" and "NodeAssembler" are all now separate concepts:
- NodeStyle is more or less a builder factory (forgive me -- but it's important: you can handle these without causing allocations, and that matters).
Use NodeStyle to tell library functions what kind of in-memory representation you want to use for your data. (Typically `basicnode.Style.Any` will do -- but you have the control to choose others.)
- NodeBuilder allocates and begins the assembly of a value (or a whole tree of values, which may be allocated all at once).
- NodeAssembler is the recursive part of assembling a value (NodeBuilder implements NodeAssembler, but everywhere other than the root, you only use the NodeAssembler interface).
- Assembly of trees of values now simply involves asking the assembler for a recursive node to give you assemblers for the keys and/or values, and then simply... using them.
- This is much simpler (and also faster) to use than the previous system, which involved an awkward dance to ask about what kind the child nodes were, get builders for them, use those builders, then put the result pack in the parent, and so forth.
- Creating new maps and lists now accepts a size hint argument.
- This isn't strictly enforced (you can provide zero, or even a negative number to indicate "I don't know", and still add data to the assembler), but may improve efficiency by reducing reallocation costs to grow structures if the size can be estimated in advance.
- Expect **sizable** performance improvements in this version, due to these interface changes.
- Some packages were renamed in an effort to improve naming consistency and feel:
- The default node implementations have moved: expect to replace `impl/free` in your package imports with `node/basic` (which is an all around better name, anyway).
- The codecs packages have moved: replace `encoding` with `codec` in your package imports (that's all there is to it; nothing else changed).
- Previous demos of code generation are currently broken / disabled / removed in this tag.
- ...but they'll return in future versions, and you can follow along in branches if you wish.
- Bugfix: dag-cbor codec now correctly handles marshalling when bytes come after a link in the same object. [[53](https://github.com/ipld/go-ipld-prime/pull/53)]
### v0.0.2
- Many various performance improvements, fixes, and docs improvements.
- Many benchmarks and additional tests introduced.
- Includes early demos of parts of the schema system, and early demos of code generation.
- Mostly a checkpoint before beginning v0.0.3, which involved many large API reshapings.
### v0.0.1
- Our very first tag!
- The central `Node` and `NodeBuilder` interfaces are already established, as is `Link`, `Loader`, and so forth.
You can already build generic data handling using IPLD Data Model concepts with these core interfaces.
- Selectors and traversals are available.
- Codecs for dag-cbor and dag-json are batteries-included in the repo.
- There was quite a lot of work done before we even started tagging releases :)

120
vendor/github.com/ipld/go-ipld-prime/HACKME.md generated vendored Normal file
View File

@@ -0,0 +1,120 @@
hackme
======
Design rational are documented here.
This doc is not necessary reading for users of this package,
but if you're considering submitting patches -- or just trying to understand
why it was written this way, and check for reasoning that might be dated --
then it might be useful reading.
It may also be an incomplete doc. It's been written opportunistically.
If you don't understand the rationale for some things, try checking git history
(many of the commit messages are downright bookish), or get in touch via
a github issue, irc, matrix, etc and ask!
about NodeAssembler and NodeBuilder
-----------------------------------
See the godoc on these types.
In short, a `NodeBuilder` is for creating a new piece of memory;
a `NodeAssembler` is for instantiating some memory which you already have.
Generally, you'll start any function using a `NodeBuilder`, but then continue
and recurse by passing on the `NodeAssembler`.
See the `./HACKME_builderBehaviors.md` doc for more details on
high level rules and implementation patterns to look out for.
about NodePrototype
---------------
### NodePrototype promises information without allocations
You'll notice nearly every `ipld.NodePrototype` implementation is
a golang struct type with _zero fields_.
This is important.
Getting a NodePrototype is generally expected to be "free" (i.e., zero allocations),
while `NewBuilder` is allowed to be costly (usually causes at least one allocation).
Zero-member structs can be referred to by an interface without requiring an allocation,
which is how it's possible ensure `NodePrototype` are always "free" to refer to.
(Note that a `NodePrototype` that bundles some information like ADL configuration
will subvert this pattern -- but these are an exception, not the rule.)
### NodePrototype reported by a Node
`ipld.NodePrototype` is a type that opaquely represents some information about how
a node was constructed and is implemented. The general contract for what
should happen when asking a node about its prototype
(via the `ipld.Node.Prototype() NodePrototype` interface) is that prototype should contain
effective instructions for how one could build a copy of that node, using
the same implementation details.
By example, if some node `n` was made as a `basicnode.plainString`,
then `n.Prototype()` will be `basicnode.Prototype.String`,
and `n.Prototype().NewBuilder().AssignString("xyz")` can be presumed to work.
Note there are also limits to this: if a node was built in a flexible way,
the prototype it reports later may only report what it is now, and not return
that same flexibility again.
By example, if something was made as an "any" -- i.e.,
via `basicnode.Prototype.Any.NewBuilder()`, and then *happened* to be assigned a string value --
the resulting node will still carry a `Prototype()` property that returns
`basicnode.Prototype.String` -- **not** `basicnode.Prototype.Any`.
#### NodePrototype meets generic transformation
One of the core purposes of the `NodePrototype` interface (and all the different
ways you can get it from existing data) is to enable the `traversal` package
(or other user-written packages like it) to do transformations on data.
// work-in-progress warning: generic transformations are not fully implemented.
When implementating a transformation that works over unknown data,
the signiture of function a user provides is roughly:
`func(oldValue Node, acceptableValues NodePrototype) (Node, error)`.
(This signiture may vary by the strategy taken by the transformation -- this
signiture is useful because it's capable of no-op'ing; an alternative signiture
might give the user a `NodeAssembler` instead of the `NodePrototype`.)
In this situation, the transformation system determines the `NodePrototype`
(or `NodeAssembler`) to use by asking the parent value of the one we're visiting.
This is because we want to give the update function the ability to create
any kind of value that would be accepted in this position -- not just create a
value of the same prototype as the one currently there! It is for this reason
the `oldValue.Prototype()` property can't be used directly.
At the root of such a transformation, we use the `node.Prototype()` property to
determine how to get started building a new value.
#### NodePrototype meets recursive assemblers
Asking for a NodePrototype in a recursive assembly process tells you about what
kind of node would be accepted in an `AssignNode(Node)` call.
It does *not* make any remark on the fact it's a key assembler or value assembler
and might be wrapped with additional rules (such as map key uniqueness, field
name expectations, etc).
(Note that it's also not an exclusive statement about what `AssignNode(Node)` will
accept; e.g. in many situations, while a `Prototype.MyStringType` might be the prototype
returned, any string kinded node can be used in `AssignNode(Node)` and will be
appropriately converted.)
Any of these paths counts as "recursive assembly process":
- `MapAssembler.KeyPrototype()`
- `MapAssembler.ValuePrototype(string)`
- `MapAssembler.AssembleKey().Prototype()`
- `MapAssembler.AssembleValue().Prototype()`
- `ListAssembler.ValuePrototype()`
- `ListAssembler.AssembleValue().Prototype()`
### NodePrototype for carrying ADL configuration
// work-in-progress warning: this is an intention of the design, but not implemented.

View File

@@ -0,0 +1,61 @@
hackme: NodeBuilder and NodeAssembler behaviors
===============================================
high level rules of builders and assemblers
-------------------------------------------
- Errors should be returned as soon as possible.
- That means an error like "repeated key in map" should be returned by the key assembler!
- Either 'NodeAssembler.AssignString' should return this (for simple keys on untyped maps, or on structs, etc)...
- ... or 'MapAssembler.Finish' (in the case of complex keys in a typed map).
- Logical integrity checks must be done locally -- recursive types rely on their contained types to report errors, and the recursive type wraps the assemblers of their contained type in order to check and correctly invalidate/rollback the recursive construction.
- Recursive types tend to have a value assembler that wraps the child type's assembler in order to intercept relevant "finish" methods.
- This is generally where that logic integrity check mentioned above is tracked; we need explicit confirmation that it *passes* before the parent's assembly should proceed.
- Implementations may also need this moment to complete any assignment of the child value into position in the parent value. But not all implementations need this -- some will have had all the child assembler effects applying directly to the final memory positions.
- Assemblers should invalidate themselves as soon as they become "finished".
- For maps and lists, that means the "Finish" methods.
- For all the other scalars, the "Assign*" method itself means finished.
- Or in other words: whatever method returns an `error`, that's what makes that assembler "finished".
- The purpose of this is to prevent accidental mutation after any validations have been performed during the "finish" processing.
- Many methods must be called in the right order, and the user must not hold onto references after calling "finish" methods on them.
- The reason this is important is to enable assembler systems to agressively reuse memory, thus increasing performance.
- Thus, if you hold onto NodeAssembler reference after being finished with it... you can't assume it'll explicitly error if you call further methods on it, because it might now be operating again... _on a different target_.
- In recursive structures, calling AssembleKey or AssembleValue might return pointer-identical assemblers (per warning in previous bullet), but the memory their assembly is targetted to should always advance -- it should never target already-assembled memory.
- (If you're thinking "the Rust memory model would be able to greatly enhance safety here!"... yes. Yes it would.)
- When misuses of order are detected, these may cause panics (rather than error returns) (not all methods that can be so misused have error returns).
detailed rules and expectations for implementers
------------------------------------------------
The expectations in the "happy path" are often clear.
Here are also collected some details of exactly what should happen when an error has been reached,
but the caller tries to continue anyway.
- while building maps:
- assigning a key with 'AssembleKey':
- in case of success: clearly 'AssembleValue' should be ready to use next.
- in case of failure from repeated key:
- the error must be returned immediately from either the 'NodeAssembler.AssignString' or the 'MapAssembler.Finish' method.
- 'AssignString' for any simple keys; 'MapAssembler.Finish' may be relevant in the case of complex keys in a typed map.
- implementers take note: this implies the `NodeAssembler` returned by `AssembleKey` has some way to refer to the map assembler that spawned it.
- no side effect should be visible if 'AssembleKey' is called again next.
- (typically this doesn't require extra code for the string case, but it may require some active zeroing in the complex key case.)
- (remember to reset any internal flag for expecting 'AssembleValue' to be used next, and decrement any length pointers that were optimistically incremented!)
- n.b. the "no side effect" rule here is for keys, not for values.
- TODO/REVIEW: do we want the no-side-effect rule for values? it might require nontrivial volumes of zeroing, and often in practice, this might be wasteful.
- invalidation of assemblers:
- is typically implemented by nil'ing the wip node they point to.
- this means you get nil pointer dereference panics when attempting to use an assembler after it's finished... which is not the greatest error message.
- but it does save us a lot of check code for a situation that the user certainly shouldn't get into in the first place.
- (worth review: can we add that check code without extra runtime cost? possibly, because the compiler might then skip its own implicit check branches. might still increase SLOC noticably in codegen output, though.)
- worth noting there's a limit to how good this can be anyway: it's "best effort" error reporting: see the remarks on reuse of assembler memory in "overall rules" above.
- it's systemically critical to not yield an assembler _ever again in the future_ that refers to some memory already considered finished.
- even though we no longer return intermediate nodes, there's still many ways this could produce problems. For example, complicating (if not outright breaking) COW sharing of segments of data.
- in most situations, we get this for free, because the child assembler methods only go "forward" -- there's no backing up, lists have no random index insertion or update support, and maps actively reject dupe keys.
- if you *do* make a system which exposes any of those features... be very careful; you will probably need to start tracking "freeze" flags on the data in order to retain systemic sanity.

View File

@@ -0,0 +1,42 @@
hacking: merge strategies
=========================
This is a short document about how the maintainers of this repo handle branches and merging.
It's useful information for a developer wanting to contribute, but otherwise unimportant.
---
We prefer to:
1. Do development on a branch;
2. Before merge, rebase onto master (if at all possible);
- if there are individual commit hashes that should be preserved because they've been referenced outside the project, say so; we don't want to have to presume this by default.
- rebasing your commits (or simply staging them carefully the first time; `git add -p` is your friend) for clarity of later readers is greatly appreciated.
3. Merge, using the "--no-ff" strategy. The github UI does fine at this.
There are a couple of reasons we prefer this:
- Squashing, if appropriate, can be done by the author. We don't use github's squash button because it's sometimes quite difficult to make a good combined commit message without effort to do so by the diff's author, so it's best left to that author to do themselves.
- Generating a merge commit gives a good place for github to insert the PR link, if a PR has been used. This is good info to have if someone is later reading git history and wants to see links to where other discussion may have taken place.
- We *do* like fairly linearly history. Emphasis on "fairly" -- it doesn't have to be perfectly lock-step ridgidly linear: but when doing `git log --graph`, we also want to not see more than a handful of lines running in parallel at once. (Too many parallel branches at once is both unpleasant to read and review later, and can indicative of developmental process issues, so it's a good heuristic to minimize for multiple reasons.) Rebasing before generating a merge commit does this: if consistently done, `git log --graph` will yield two parallel lines at all times.
- Generating a merge commit, when combined with rebasing the commits on the branch right before merge, means `git log --graph` will group up the branch commits in a visually clear way. Preserving this relation can be useful. (Neither squashing nor rebase-without-merge approaches preserve this information.)
Mind, all of these rules are heuristics and "rules of thumb". Some small changes are also perfectly reasonable to land with either a squash or rebase that appends them linearly onto history.
The maintainers may choose strategies as they see fit depending on the size of the content and the level of interest in preserving individual commits and their messages and relational history.
What does this mean for PRs?
----------------------------
- Please keep PRs rebased on top of master as much as possible.
- If you decide you want multiple commits with distinct messages, fine. If you want to squash, also fine.
- If you are linking to commit hashes and don't want them rebased, please comment about this;
otherwise, it should not be presumed we'll keep exact commit hashes reachable.
The maintainers also reserve the right to rebase or squash things at their own option;
we'll comment explicitly if committing to not do so, and it should not otherwise be presumed.
- If you're not comfortable with rebase: fine. Just be aware that if a PR branches off master for quite some time,
and it does become ready for merge later, the maintainers are likely to squash/rebase your work for you before merging.

View File

@@ -0,0 +1,69 @@
# Making go-ipld-prime Releases
## Versioning strategy
go-ipld-prime follows **[WarpVer](https://gist.github.com/warpfork/98d2f4060c68a565e8ad18ea4814c25f)**, a form of SemVer that never bumps the major version number and uses minor version numbers to indicate degree of *changeness*: **even numbers should be easy upgrades; odd numbers may change things**. The patch version number is rarely used in this scheme.
## CHANGELOG.md
There is a CHANGELOG.md, it should be relevant and updated. Notable items in the commit history since the last release should be included. Where possible and practical, links to relevant pull requests or other issues with discussions on the items should be included.
To find the list of commits, it is recommended that you use a tool that can provide some extra metadata to help with matching commits to pull requests. [changelog-maker](https://github.com/nodejs/changelog-maker) can help with this (requires Node.js be installed and the `npx` command be available):
```
npx changelog-maker --start-ref=v0.16.0 --reverse=true --find-matching-prs=true --md=true ipld go-ipld-prime
```
Alternatively, you can use `git log` and perform mapping to pull requests manually, e.g.:
```
git log --all --graph --date-order --abbrev-commit --decorate --oneline
```
*(where `--start-ref` points to name of the previous release tag)*
### Curate and summarize
The CHANGELOG should be informative for developers wanting to know what changes may pose a risk (highlight these!) and what changes introduce features they may be interested in using.
1. Group commits to subsystem to create a two-level list. Subsections can include "Data Model", "Schemas", "Bindnode", "Selectors", "Codecs", and the meta-category of "Build" to describe changes local to the repository and not necessarily relevant to API consumers.
2. If there are breaking, or potentially breaking changes, list them under a `#### 🛠 Breaking Changes` section.
3. Otherwise, prune the list of commits down to the set of changes relevant to users, and list them under a `#### 🔦 Highlights` section.
Note that there is also a **Planned/Upcoming Changes** section near the top of the CHANGELOG.md. Update this to remove _done_ items and add other items that may be nearing completion but not yet released.
### Call-outs
Add "special thanks" call-outs to individuals who have contributed meaningful changes to the release.
## Propose a release
After updating the CHANGELOG.md entry, also bump the version number appropriately in **version.json** file so the auto-release procedure can take care of tagging for you.
Commit and propose the changes via a pull request to ipld/go-ipld-prime.
## Release
After a reasonable amount of time for feedback (usually at least a full global business day), the changes can be merged and a release tag will be created by the GitHub Actions.
Use the GitHub UI to make a [release](https://github.com/ipld/go-ipld-prime/releases), copying in the contents of the CHANGELOG.md for that release.
Drop in a note to the appropriate Matrix/Discord/Slack channel(s) for IPLD about the release.
Optional: Protocol Labs staff can send an email to shipped@protocol.ai to describe the release, these are typically well-read and appreciated.
## Checklist
Prior to opening a release proposal pull request, create an issue with the following markdown checklist to help ensure the requisite steps are taken. The issue can also be used to alert subscribed developers to the timeframe and the approximate scope of changes in the release.
```markdown
* [ ] Add new h3 to `CHANGELOG.md` under **Released Changes** with curated and subsystem-grouped list of changes and links to relevant PRs
* [ ] Highlight any potentially breaking or disruptive changes under "🛠 Breaking Changes", including extended descriptions to help users make compatibility judgements
* [ ] Add special-thanks call-outs to contributors making significant contributions
* [ ] Update **Planned/Upcoming Changes** section to remove completed items and add newly upcoming, but incomplete items
* [ ] Bump version number appropriately in `version.json`
* [ ] Propose release via pull request, merge after enough time for async global feedback
* [ ] Create GitHub [release](https://github.com/ipld/go-ipld-prime/releases) with the new tag, copying the new `CHANGELOG.md` contents
* [ ] Announce on relevant Discord/Matrix/Slack channel(s)
* [ ] (Optional) Announce to shipped@protocol.ai
```

21
vendor/github.com/ipld/go-ipld-prime/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Eric Myhre
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

236
vendor/github.com/ipld/go-ipld-prime/README.md generated vendored Normal file
View File

@@ -0,0 +1,236 @@
go-ipld-prime
=============
`go-ipld-prime` is an implementation of the IPLD spec interfaces,
a batteries-included codec implementations of IPLD for CBOR and JSON,
and tooling for basic operations on IPLD objects (traversals, etc).
API
---
The API is split into several packages based on responsibly of the code.
The most central interfaces are the base package,
but you'll certainly need to import additional packages to get concrete implementations into action.
Roughly speaking, the core package interfaces are all about the IPLD Data Model;
the `codec/*` packages contain functions for parsing serial data into the IPLD Data Model,
and converting Data Model content back into serial formats;
the `traversal` package is an example of higher-order functions on the Data Model;
concrete `ipld.Node` implementations ready to use can be found in packages in the `node/*` directory;
and several additional packages contain advanced features such as IPLD Schemas.
(Because the codecs, as well as higher-order features like traversals, are
implemented in a separate package from the core interfaces or any of the Node implementations,
you can be sure they're not doing any funky "magic" -- all this stuff will work the same
if you want to write your own extensions, whether for new Node implementations
or new codecs, or new higher-order order functions!)
- `github.com/ipld/go-ipld-prime` -- imported as just `ipld` -- contains the core interfaces for IPLD. The most important interfaces are `Node`, `NodeBuilder`, `Path`, and `Link`.
- `github.com/ipld/go-ipld-prime/node/basicnode` -- provides concrete implementations of `Node` and `NodeBuilder` which work for any kind of data, using unstructured memory.
- `github.com/ipld/go-ipld-prime/node/bindnode` -- provides concrete implementations of `Node` and `NodeBuilder` which store data in native golang structures, interacting with it via reflection. Also supports IPLD Schemas!
- `github.com/ipld/go-ipld-prime/traversal` -- contains higher-order functions for traversing graphs of data easily.
- `github.com/ipld/go-ipld-prime/traversal/selector` -- contains selectors, which are sort of like regexps, but for trees and graphs of IPLD data!
- `github.com/ipld/go-ipld-prime/codec` -- parent package of all the codec implementations!
- `github.com/ipld/go-ipld-prime/codec/dagcbor` -- implementations of marshalling and unmarshalling as CBOR (a fast, binary serialization format).
- `github.com/ipld/go-ipld-prime/codec/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` and `schema.TypedNode` interface declarations, which represent IPLD Schema type information.
- `github.com/ipld/go-ipld-prime/node/typed` -- provides concrete implementations of `schema.TypedNode` which decorate a basic `Node` at runtime to have additional features described by IPLD Schemas.
Getting Started
---------------
Let's say you want to create some data programmatically,
and then serialize it, or save it as [blocks].
You've got a ton of different options, depending on what golang convention you want to use:
- the `qp` package -- [example](https://pkg.go.dev/github.com/ipld/go-ipld-prime/fluent/qp#example-package)
- the `bindnode` system, if you want to use golang types -- [example](https://pkg.go.dev/github.com/ipld/go-ipld-prime/node/bindnode#example-Wrap-NoSchema), [example with schema](https://pkg.go.dev/github.com/ipld/go-ipld-prime/node/bindnode#example-Wrap-WithSchema)
- or the [`NodeBuilder`](https://pkg.go.dev/github.com/ipld/go-ipld-prime/datamodel#NodeBuilder) interfaces, raw (verbose; not recommended)
- or even some codegen systems!
Once you've got a Node full of data,
you can serialize it:
https://pkg.go.dev/github.com/ipld/go-ipld-prime#example-package-CreateDataAndMarshal
But probably you want to do more than that;
probably you want to store this data as a block,
and get a CID that links back to it.
For this you use `LinkSystem`:
https://pkg.go.dev/github.com/ipld/go-ipld-prime/linking#example-LinkSystem.Store
Hopefully these pointers give you some useful getting-started focal points.
The API docs should help from here on out.
We also highly recommend scanning the [godocs](https://pkg.go.dev/github.com/ipld/go-ipld-prime) for other pieces of example code, in various packages!
Let us know in [issues](https://github.com/ipld/go-ipld-prime/issues), [chat, or other community spaces](https://ipld.io/docs/intro/community/) if you need more help,
or have suggestions on how we can improve the getting-started experiences!
Other IPLD Libraries
--------------------
The IPLD specifications are designed to be language-agnostic.
Many implementations exist in a variety of languages.
For overall behaviors and specifications, refer to the IPLD website, or its source, in IPLD meta repo:
- https://ipld.io/
- https://github.com/ipld/ipld/
You should find specs in the `specs/` dir there,
human-friendly docs in the `docs/` dir,
and information about _why_ things are designed the way they are mostly in the `design/` directories.
There are also pages in the IPLD website specifically about golang IPLD libraries,
and your alternatives: https://ipld.io/libraries/golang/
### distinctions from go-ipld-interface&go-ipld-cbor
This library ("go ipld prime") is the current head of development for golang IPLD,
and we recommend new developments in golang be done using this library as the basis.
However, several other libraries exist in golang for working with IPLD data.
Most of these predate go-ipld-prime and no longer receive active development,
but since they do support a lot of other software, you may continue to seem them around for a while.
go-ipld-prime is generally **serially compatible** with these -- just like it is with IPLD libraries in other languages.
In terms of programmatic API and features, go-ipld-prime is a clean take on the IPLD interfaces,
and chose to address several design decisions very differently than older generation of libraries:
- **The Node interfaces map cleanly to the IPLD Data Model**;
- Many features known to be legacy are dropped;
- The Link implementations are purely CIDs (no "name" nor "size" properties);
- The Path implementations are provided in the same box;
- The JSON and CBOR implementations are provided in the same box;
- Several odd dependencies on blockstore and other interfaces that were closely coupled with IPFS are replaced by simpler, less-coupled interfaces;
- New features like IPLD Selectors are only available from go-ipld-prime;
- New features like ADLs (Advanced Data Layouts), which provide features like transparent sharding and indexing for large data, are only available from go-ipld-prime;
- Declarative transformations can be applied to IPLD data (defined in terms of the IPLD Data Model) using go-ipld-prime;
- and many other small refinements.
In particular, the clean and direct mapping of "Node" to concepts in the IPLD Data Model
ensures a much more consistent set of rules when working with go-ipld-prime data, regardless of which codecs are involved.
(Codec-specific embellishments and edge-cases were common in the previous generation of libraries.)
This clarity is also what provides the basis for features like Selectors, ADLs, and operations such as declarative transformations.
Many of these changes had been discussed for the other IPLD codebases as well,
but we chose clean break v2 as a more viable project-management path.
Both go-ipld-prime and these legacy libraries can co-exist on the same import path, and both refer to the same kinds of serial data.
Projects wishing to migrate can do so smoothly and at their leisure.
We now consider many of the earlier golang IPLD libraries to be defacto deprecated,
and you should expect new features *here*, rather than in those libraries.
(Those libraries still won't be going away anytime soon, but we really don't recomend new construction on them.)
### migrating
**For recommendations on where to start when migrating:**
see [README_migrationGuide](./README_migrationGuide.md).
That document will provide examples of which old concepts and API names map to which new APIs,
and should help set you on the right track.
### unixfsv1
Lots of people who hear about IPLD have heard about it through IPFS.
IPFS has IPLD-native APIs, but IPFS *also* makes heavy use of a specific system called "UnixFSv1",
so people often wonder if UnixFSv1 is supported in IPLD libraries.
The answer is "yes" -- but it's not part of the core.
UnixFSv1 is now treated as an [ADL](https://ipld.io/glossary/#adl),
and a go-ipld-prime compatible implementation can be found
in the [ipfs/go-unixfsnode](https://github.com/ipfs/go-unixfsnode) repo.
Additionally, the codec used in UnixFSv1 -- dag-pb --
can be found implemented in the [ipld/go-codec-dagpb](https://github.com/ipld/go-codec-dagpb) repo.
A "some assembly required" advisory may still be in effect for these pieces;
check the readmes in those repos for details on what they support.
The move to making UnixFSv1 a non-core system has been an arduous retrofit.
However, framing it as an ADL also provides many advantages:
- it demonstrates that ADLs as a plugin system _work_, and others can develop new systems in this pattern!
- it has made pathing over UnixFSv1 much more standard and well-defined
- this standardization means systems like [Selectors](https://ipld.io/glossary/#selectors) work naturally over UnixFSv1...
- ... which in turn means anything using them (ex: CAR export; graphsync; etc) can very easily be asked to produce a merkle-proof
for a path over UnixFSv1 data, without requiring the querier to know about the internals. Whew!
We hope users and developers alike will find value in how these systems are now layered.
Change Policy
-------------
The go-ipld-prime library is ready to use, and we value stability highly.
We make releases periodically.
However, using a commit hash to pin versions precisely when depending on this library is also perfectly acceptable.
(Only commit hashes on the master branch can be expected to persist, however;
depending on a commit hash in a branch is not recommended. See [development branches](#development-branches).)
We maintain a [CHANGELOG](CHANGELOG.md)!
Please read it, when updating!
We do make reasonable attempts to minimize the degree of changes to the library which will create "breaking change" experiences for downstream consumers,
and we do document these in the changelog (often, even with specific migration instructions).
However, we do also still recommend running your own compile and test suites as a matter of course after updating.
You can help make developing this library easier by staying up-to-date as a downstream consumer!
When we do discover a need for API changes, we typically try to introduce the new API first,
and do at least one release tag in which the old API is deprecated (but not yet removed).
We will all be able to develop software faster, together, as an ecosystem,
if libraries can keep reasonably closely up-to-date with the most recent tags.
### Version Names
When a tag is made, version number steps in go-ipld-prime advance as follows:
1. the number bumps when the lead maintainer says it does.
2. even numbers should be easy upgrades; odd numbers may change things.
3. the version will start with `v0.` until further notice.
[This is WarpVer](https://gist.github.com/warpfork/98d2f4060c68a565e8ad18ea4814c25f).
These version numbers are provided as hints about what to expect,
but ultimately, you should always invoke your compiler and your tests to tell you about compatibility,
as well as read the [changelog](CHANGELOG.md).
### Updating
**Read the [CHANGELOG](CHANGELOG.md).**
Really, read it. We put exact migration instructions in there, as much as possible. Even outright scripts, when feasible.
An even-number release tag is usually made very shortly before an odd number tag,
so if you're cautious about absorbing changes, you should update to the even number first,
run all your tests, and *then* upgrade to the odd number.
Usually the step to the even number should go off without a hitch, but if you *do* get problems from advancing to an even number tag,
A) you can be pretty sure it's a bug, and B) you didn't have to edit a bunch of code before finding that out.
### Development branches
The following are norms you can expect of changes to this codebase, and the treatment of branches:
- The `master` branch will not be force-pushed.
- (exceptional circumstances may exist, but such exceptions will only be considered valid for about as long after push as the "$N-second-rule" about dropped food).
- Therefore, commit hashes on master are gold to link against.
- All other branches *can* be force-pushed.
- Therefore, commit hashes not reachable from the master branch are inadvisable to link against.
- If it's on master, it's understood to be good, in as much as we can tell.
- Changes and features don't get merged until their tests pass!
- Packages of "alpha" developmental status may exist, and be more subject to change than other more finalized parts of the repo, but their self-tests will at least pass.
- Development proceeds -- both starting from and ending on -- the `master` branch.
- There are no other long-running supported-but-not-master branches.
- The existence of tags at any particular commit do not indicate that we will consider starting a long running and supported diverged branch from that point, nor start doing backports, etc.

View File

@@ -0,0 +1,59 @@
A short guide to migrating to go-ipld-prime from other repos
============================================================
Here are some quick notes on APIs that you might've been using
if you worked with IPLD in golang before go-ipld-prime,
and a pointer to what you should check out for an equivalent
in order to upgrade your code to use go-ipld-prime.
(Let us know if there are more pointers we should add to this list to ease your journey,
or someone else's future journey!)
- Were you using [ipfs/go-datastore](https://pkg.go.dev/github.com/ipfs/go-datastore) APIs?
- You can wrap those in `storage/dsadapter` and keep using them.
You can also plug that into `linking.LinkSystem` to get higher level IPLD operations.
- Or if you were only using datastore because of some specific implementation of it, like, say `flatfs`?
Then check out the possibility of moving all the way directly to new implementations like `storage/fsstore`.
- Were you using [ipfs/go-ipfs-blockstore](https://pkg.go.dev/github.com/ipfs/go-ipfs-blockstore) APIs?
- You can wrap those in `storage/bsadapter` and keep using them.
You can also plug that into `linking.LinkSystem` to get higher level IPLD operations.
(This is almost exactly the same; we've just simplified in the interface, made it easier to implement, and cleaned up inconsistencies with the other interfaces in this migration guide which were already very very similar.)
- Were you using [ipfs/go-blockservice](https://pkg.go.dev/github.com/ipfs/go-blockservice) APIs?
- You can wrap those in `storage/bsrvadapter` and keep using them.
You can also plug that into `linking.LinkSystem` to get higher level IPLD operations.
- Be judicious about whether you actually want to do this.
Plugging in the potential to experience unknown network latencies into code that's expecting predictable local lookup speeds
may have undesirable performance outcomes. (But if you want to do it, go ahead...)
- Were you using [ipfs/go-ipld-format.DAGService](https://pkg.go.dev/github.com/ipfs/go-ipld-format#DAGService)?
- If you're using the `Add` and `Get` methods: those are now on `linking.LinkSystem`, as `Store` and `Load`.
- If you're using the `*Many` methods: those don't have direct replacements;
we don't use APIs that force that style of future-aggregation anymore, because it's bad for pipelining.
Just use `Store` and `Load`.
- Were you using [ipfs/go-ipld-format.Node](https://pkg.go.dev/github.com/ipfs/go-ipld-format#Node)?
- That's actually a semantic doozy -- we use the word "node" _much_ differently now.
See https://ipld.io/glossary/#node for an introduction.
(Long story short: the old "node" was often more like a whole block, and the new "node" is more like an AST node: there's lots of "node" in a "block" now.)
- There's an `datamodel.Node` interface -- it's probably what you want.
(You just might have to update how you think about it -- see above bullet point about semantics shift.)
It's also aliased as the `ipld.Node` interface here right in the root package, because it's such an important and central interface that you'll be using it all the time.
- Are you specifically looking for how to get a list of links out of a block?
That's a `LinkSystem.Load` followed by applying `traversal.SelectLinks` on the node it gives you: now you've got the list of links from that node (and its children, recursively, as long as they came from the same block of raw data), tada!
- Are you looking for the equivalent of [ipfs/go-ipld-format.Link](https://pkg.go.dev/github.com/ipfs/go-ipld-format#Link)?
- That's actually a feature specific to dag-pb, and arguably even specific to unixfsv1.
There is no equivalent in go-ipld-prime as a result.
But you'll find it in other libraries that are modern and go-ipld-prime based: see below.
- Were you using some feature specific to dag-pb?
- There's an updated dag-pb codec found in https://github.com/ipld/go-codec-dagpb -- it should have what you need.
- Does it turn out you actually meant you're using some feature specific to unixfsv1?
(Yeah -- that comes up a lot. It's one of the reasons we're deprecating so many of the old libraries -- they make it _really_ easy to confuse this.)
Then hang on for the next bullet point, which is for you! :)
- Were you using some features of unixfsv1?
- Check out the https://github.com/ipfs/go-unixfsnode library -- it should have what you need.
- Pathing, like what used to be found in ipfs/go-path? That's here now.
- Sharding? That's here now too.
- You probably don't need incentives if you're here already, but can we quickly mention that... you can use Selectors over these? Neat!
If you're encountering more questions about how to migrate some code,
please jump in to the ipld chat via either [matrix](https://matrix.to/#/#ipld:ipfs.io) or discord (or any other bridge)
and ask!
We want to grow this list of pointers to be as encompassing and helpful as possible.

7
vendor/github.com/ipld/go-ipld-prime/adl.go generated vendored Normal file
View File

@@ -0,0 +1,7 @@
package ipld
import (
"github.com/ipld/go-ipld-prime/adl"
)
type ADL = adl.ADL

25
vendor/github.com/ipld/go-ipld-prime/adl/interface.go generated vendored Normal file
View File

@@ -0,0 +1,25 @@
package adl
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// ADL is an interface denoting an Advanced Data Layout,
// which is something that supports all the datamodel.Node operations,
// but may be doing so using some custom internal logic.
//
// For more details, see the docs at
// https://ipld.io/docs/advanced-data-layouts/ .
//
// This interface doesn't specify much new behavior, but it does include
// the requirement of a way to tell an examiner about your "substrate",
// since this concept does seem to be present in all ADLs.
type ADL interface {
datamodel.Node
// Substrate returns the underlying Data Model node, which can be used
// to encode an ADL's raw layout.
//
// Note that the substrate of an ADL can contain other ADLs!
Substrate() datamodel.Node
}

10
vendor/github.com/ipld/go-ipld-prime/codec.go generated vendored Normal file
View File

@@ -0,0 +1,10 @@
package ipld
import (
"github.com/ipld/go-ipld-prime/codec"
)
type (
Encoder = codec.Encoder
Decoder = codec.Decoder
)

77
vendor/github.com/ipld/go-ipld-prime/codec/README.md generated vendored Normal file
View File

@@ -0,0 +1,77 @@
Codecs
======
The `go-ipld-prime/codec` package is a grouping package.
The subpackages contains some codecs which reside in this repo.
The codecs included here are our "batteries included" codecs,
but they are not otherwise special.
It is not necessary for a codec to be a subpackage here to be a valid codec to use with go-ipld;
anything that implements the `codec.Encoder` and `codec.Decoder` interfaces is fine.
Terminology
-----------
We generally refer to "codecs" as having an "encode" function and "decode" function.
We consider "encoding" to be the process of going from {Data Model} to {serial data},
and "decoding" to be the process of going from {serial data} to {Data Model}.
### Codec vs Multicodec
A "codec" is _any_ function that goes from {Data Model} to {serial data}, or vice versa.
A "multicodec" is a function which does that and is _also_ specifically recognized and described in
the tables in https://github.com/multiformats/multicodec/ .
Multicodecs generally leave no further room for customization and configuration,
because their entire behavior is supposed to be specified by a multicodec indicator code number.
Our codecs, in the child packages of this one, usually offer configuration options.
They also usually offer exactly one function, which does *not* allow configuration,
which is supplying a multicodec-compatible behavior.
You'll see this marked in the docs on those functions.
### Marshal vs Encode
It's common to see the terms "marshal" and "unmarshal" used in golang.
Those terms are usually describing when structured data is transformed into linearized, tokenized data
(and then, perhaps, all the way to serially encoded data), or vice versa.
We would use the words the same way... except we don't end up using them,
because that feature doesn't really come up in our codec layer.
In IPLD, we would describe mapping some typed data into Data Model as "marshalling".
(It's one step shy of tokenizing, but barely: Data Model does already have defined ordering for every element of data.)
And we do have systems that do this:
`bindnode` and our codegen systems both do this, implicitly, when they give you an `ipld.Node` of the representation of some data.
We just don't end up talking about it as "marshalling" because of how it's done implicitly by those systems.
As a result, all of our features relating to codecs only end up speaking about "encoding" and "decoding".
### Legacy code
There are some appearances of the words "marshal" and "unmarshal" in some of our subpackages here.
That verbiage is generally on the way out.
For functions and structures with those names, you'll notice their docs marking them as deprecated.
Why have "batteries-included" codecs?
-------------------------------------
These codecs live in this repo because they're commonly used, highly supported,
and general-purpose codecs that we recommend for widespread usage in new developments.
Also, it's just plain nice to have something in-repo for development purposes.
It makes sure that if we try to make any API changes, we immediately see if they'd make codecs harder to implement.
We also use the batteries-included codecs for debugging, for test fixtures, and for benchmarking.
Further yet, the batteries-included codecs let us offer getting-started APIs.
For example, we offer some helper APIs which use codecs like e.g. JSON to give consumers of the libraries
one-step helper methods that "do the right thing" with zero config... so long as they happen to use that codec.
Even for consumers who don't use those codecs, such functions then serve as natural documentation
and examples for what to do to put their codec of choice to work.

128
vendor/github.com/ipld/go-ipld-prime/codec/api.go generated vendored Normal file
View File

@@ -0,0 +1,128 @@
package codec
import (
"io"
"github.com/ipld/go-ipld-prime/datamodel"
)
// The following two types define the two directions of transform that a codec can be expected to perform:
// from Node to serial stream (aka "encoding", sometimes also described as "marshalling"),
// and from serial stream to Node (via a NodeAssembler) (aka "decoding", sometimes also described as "unmarshalling").
//
// You'll find a couple of implementations matching this shape in subpackages of 'codec'.
// (These are the handful of encoders and decoders we ship as "batteries included".)
// Other encoder and decoder implementations can be found in other repositories/modules.
// It should also be easy to implement encodecs and decoders of your own!
//
// Encoder and Decoder functions can be used on their own, but are also often used via the `ipld/linking.LinkSystem` construction,
// which handles all the other related operations necessary for a content-addressed storage system at once.
//
// Encoder and Decoder functions can be registered in the multicodec table in the `ipld/multicodec` package
// if they're providing functionality that matches the expectations for a multicodec identifier.
// This table will be used by some common EncoderChooser and DecoderChooser implementations
// (namely, the ones in LinkSystems produced by the `linking/cid` package).
// It's not strictly necessary to register functions there, though; you can also just use them directly.
//
// There are furthermore several conventions that codec packages are recommended to follow, but are only conventions:
//
// Most codec packages should have a ReusableEncoder and ResuableDecoder type,
// which contain any working memory needed by the implementation, as well as any configuration options,
// and those types should have an Encode and Decode function respectively which match these function types.
// They may alternatively have EncoderConfig and DecoderConfig types, which have similar purpose,
// but aren't promising memory reuse if kept around.
//
// By convention, a codec package that expects to fulfill a multicodec contract will also have
// a package-scope exported function called Encode or Decode which also matches this interface,
// and is the equivalent of creating a zero-value ReusableEncoder or ReusableDecoder (aka, default config)
// and using its Encode or Decode methods.
// This package-scope function may also internally use a sync.Pool
// to keep some ReusableEncoder values on hand to avoid unnecesary allocations.
//
// Note that an EncoderConfig or DecoderConfig type that supports configuration options
// does not functionally expose those options when invoked by the multicodec system --
// multicodec indicator codes do not provide room for extended configuration info.
// Codecs that expose configuration options are doing so for library users to enjoy;
// it does not mean those non-default configurations will necessarly be available
// in all scenarios that use codecs indirectly.
// There is also no standard interface for such configurations: by nature,
// if they exist at all, they tend to vary per codec.
type (
// Encoder defines the shape of a function which traverses a Node tree
// and emits its data in a serialized form into an io.Writer.
//
// The dual of Encoder is a Decoder, which takes a NodeAssembler
// and fills it with deserialized data consumed from an io.Reader.
// Typically, Decoder and Encoder functions will be found in pairs,
// and will be expected to be able to round-trip each other's data.
//
// Encoder functions can be used directly.
// Encoder functions are also often used via a LinkSystem when working with content-addressed storage.
// LinkSystem methods will helpfully handle the entire process of traversing a Node tree,
// encoding this data, hashing it, streaming it to the writer, and committing it -- all as one step.
//
// An Encoder works with Nodes.
// If you have a native golang structure, and want to serialize it using an Encoder,
// you'll need to figure out how to transform that golang structure into an ipld.Node tree first.
//
// It may be useful to understand "multicodecs" when working with Encoders.
// In IPLD, a system called "multicodecs" is typically used to describe encoding foramts.
// A "multicodec indicator" is a number which describes an encoding;
// the Link implementations used in IPLD (CIDs) store a multicodec indicator in the Link;
// and in this library, a multicodec registry exists in the `codec` package,
// and can be used to associate a multicodec indicator number with an Encoder function.
// The default EncoderChooser in a LinkSystem will use this multicodec registry to select Encoder functions.
// However, you can construct a LinkSystem that uses any EncoderChooser you want.
// It is also possible to have and use Encoder functions that aren't registered as a multicodec at all...
// we just recommend being cautious of this, because it may make your data less recognizable
// when working with other systems that use multicodec indicators as part of their communication.
Encoder func(datamodel.Node, io.Writer) error
// Decoder defines the shape of a function which produces a Node tree
// by reading serialized data from an io.Reader.
// (Decoder doesn't itself return a Node directly, but rather takes a NodeAssembler as an argument,
// because this allows the caller more control over the Node implementation,
// as well as some control over allocations.)
//
// The dual of Decoder is an Encoder, which takes a Node and
// emits its data in a serialized form into an io.Writer.
// Typically, Decoder and Encoder functions will be found in pairs,
// and will be expected to be able to round-trip each other's data.
//
// Decoder functions can be used directly.
// Decoder functions are also often used via a LinkSystem when working with content-addressed storage.
// LinkSystem methods will helpfully handle the entire process of opening block readers,
// verifying the hash of the data stream, and applying a Decoder to build Nodes -- all as one step.
//
// A Decoder works with Nodes.
// If you have a native golang structure, and want to populate it with data using a Decoder,
// you'll need to either get a NodeAssembler which proxies data into that structure directly,
// or assemble a Node as intermediate storage and copy the data to the native structure as a separate step.
//
// It may be useful to understand "multicodecs" when working with Decoders.
// See the documentation on the Encoder function interface for more discussion of multicodecs,
// the multicodec table, and how this is typically connected to linking.
Decoder func(datamodel.NodeAssembler, io.Reader) error
)
// -------------------
// Errors
//
type ErrBudgetExhausted struct{}
func (e ErrBudgetExhausted) Error() string {
return "decoder resource budget exhausted (message too long or too complex)"
}
// ---------------------
// Other valuable and reused constants
//
type MapSortMode uint8
const (
MapSortMode_None MapSortMode = iota
MapSortMode_Lexical
MapSortMode_RFC7049
)

View File

@@ -0,0 +1,3 @@
package dagcbor
const linkTag = 42

View File

@@ -0,0 +1,42 @@
/*
The dagcbor package provides a DAG-CBOR codec implementation.
The Encode and Decode functions match the codec.Encoder and codec.Decoder function interfaces,
and can be registered with the go-ipld-prime/multicodec package for easy usage with systems such as CIDs.
Importing this package will automatically have the side-effect of registering Encode and Decode
with the go-ipld-prime/multicodec registry, associating them with the standard multicodec indicator numbers for DAG-CBOR.
This implementation follows most of the rules of DAG-CBOR, namely:
- by and large, it does emit and parse CBOR!
- only explicit-length maps and lists will be emitted by Encode;
- only tag 42 is accepted, and it must parse as a CID;
- only 64 bit floats will be emitted by Encode.
This implementation is also not strict about certain rules:
- Encode is order-passthrough when emitting maps (it does not sort, nor abort in error if unsorted data is encountered).
To emit sorted data, the node should be sorted before applying the Encode function.
- Decode is order-passthrough when parsing maps (it does not sort, nor abort in error if unsorted data is encountered).
To be strict about the ordering of data, additional validation must be applied to the result of the Decode function.
- Decode will accept indeterminate length lists and maps without complaint.
(These should not be allowed according to the DAG-CBOR spec, nor will the Encode function re-emit such values,
so this behavior should almost certainly be seen as a bug.)
- Decode does not consistently verify that ints and floats use the smallest representation possible (or, the 64-bit version, in the float case).
(Only these numeric encodings should be allowed according to the DAG-CBOR spec, and the Encode function will not re-emit variations,
so this behavior should almost certainly be seen as a bug.)
A note for future contributors: some functions in this package expose references to packages from the refmt module, and/or use them internally.
Please avoid adding new code which expands the visibility of these references.
In future work, we'd like to reduce or break this relationship entirely
(in part, to reduce dependency sprawl, and in part because several of
the imprecisions noted above stem from that library's lack of strictness).
*/
package dagcbor

View File

@@ -0,0 +1,381 @@
package dagcbor
import (
"fmt"
"io"
"sort"
"github.com/polydawn/refmt/cbor"
"github.com/polydawn/refmt/shared"
"github.com/polydawn/refmt/tok"
"github.com/ipld/go-ipld-prime/codec"
"github.com/ipld/go-ipld-prime/datamodel"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
)
// This file should be identical to the general feature in the parent package,
// except for the `case datamodel.Kind_Link` block,
// which is dag-cbor's special sauce for schemafree links.
// EncodeOptions can be used to customize the behavior of an encoding function.
// The Encode method on this struct fits the codec.Encoder function interface.
type EncodeOptions struct {
// If true, allow encoding of Link nodes as CBOR tag(42);
// otherwise, reject them as unencodable.
AllowLinks bool
// Control the sorting of map keys, using one of the `codec.MapSortMode_*` constants.
MapSortMode codec.MapSortMode
}
// Encode walks the given datamodel.Node and serializes it to the given io.Writer.
// Encode fits the codec.Encoder function interface.
//
// The behavior of the encoder can be customized by setting fields in the EncodeOptions struct before calling this method.
func (cfg EncodeOptions) Encode(n datamodel.Node, w io.Writer) error {
// Probe for a builtin fast path. Shortcut to that if possible.
type detectFastPath interface {
EncodeDagCbor(io.Writer) error
}
if n2, ok := n.(detectFastPath); ok {
return n2.EncodeDagCbor(w)
}
// Okay, generic inspection path.
return Marshal(n, cbor.NewEncoder(w), cfg)
}
// Future work: we would like to remove the Marshal function,
// and in particular, stop seeing types from refmt (like shared.TokenSink) be visible.
// Right now, some kinds of configuration (e.g. for whitespace and prettyprint) are only available through interacting with the refmt types;
// we should improve our API so that this can be done with only our own types in this package.
// Marshal is a deprecated function.
// Please consider switching to EncodeOptions.Encode instead.
func Marshal(n datamodel.Node, sink shared.TokenSink, options EncodeOptions) error {
var tk tok.Token
return marshal(n, &tk, sink, options)
}
func marshal(n datamodel.Node, tk *tok.Token, sink shared.TokenSink, options EncodeOptions) error {
switch n.Kind() {
case datamodel.Kind_Invalid:
return fmt.Errorf("cannot traverse a node that is absent")
case datamodel.Kind_Null:
tk.Type = tok.TNull
_, err := sink.Step(tk)
return err
case datamodel.Kind_Map:
return marshalMap(n, tk, sink, options)
case datamodel.Kind_List:
// Emit start of list.
tk.Type = tok.TArrOpen
l := n.Length()
tk.Length = int(l) // TODO: overflow check
if _, err := sink.Step(tk); err != nil {
return err
}
// Emit list contents (and recurse).
for i := int64(0); i < l; i++ {
v, err := n.LookupByIndex(i)
if err != nil {
return err
}
if err := marshal(v, tk, sink, options); err != nil {
return err
}
}
// Emit list close.
tk.Type = tok.TArrClose
_, err := sink.Step(tk)
return err
case datamodel.Kind_Bool:
v, err := n.AsBool()
if err != nil {
return err
}
tk.Type = tok.TBool
tk.Bool = v
_, err = sink.Step(tk)
return err
case datamodel.Kind_Int:
if uin, ok := n.(datamodel.UintNode); ok {
v, err := uin.AsUint()
if err != nil {
return err
}
tk.Type = tok.TUint
tk.Uint = v
} else {
v, err := n.AsInt()
if err != nil {
return err
}
tk.Type = tok.TInt
tk.Int = v
}
_, err := sink.Step(tk)
return err
case datamodel.Kind_Float:
v, err := n.AsFloat()
if err != nil {
return err
}
tk.Type = tok.TFloat64
tk.Float64 = v
_, err = sink.Step(tk)
return err
case datamodel.Kind_String:
v, err := n.AsString()
if err != nil {
return err
}
tk.Type = tok.TString
tk.Str = v
_, err = sink.Step(tk)
return err
case datamodel.Kind_Bytes:
v, err := n.AsBytes()
if err != nil {
return err
}
tk.Type = tok.TBytes
tk.Bytes = v
_, err = sink.Step(tk)
return err
case datamodel.Kind_Link:
if !options.AllowLinks {
return fmt.Errorf("cannot Marshal ipld links to CBOR")
}
v, err := n.AsLink()
if err != nil {
return err
}
switch lnk := v.(type) {
case cidlink.Link:
if !lnk.Cid.Defined() {
return fmt.Errorf("encoding undefined CIDs are not supported by this codec")
}
tk.Type = tok.TBytes
tk.Bytes = append([]byte{0}, lnk.Bytes()...)
tk.Tagged = true
tk.Tag = linkTag
_, err = sink.Step(tk)
tk.Tagged = false
return err
default:
return fmt.Errorf("schemafree link emission only supported by this codec for CID type links")
}
default:
panic("unreachable")
}
}
func marshalMap(n datamodel.Node, tk *tok.Token, sink shared.TokenSink, options EncodeOptions) error {
// Emit start of map.
tk.Type = tok.TMapOpen
expectedLength := int(n.Length())
tk.Length = expectedLength // TODO: overflow check
if _, err := sink.Step(tk); err != nil {
return err
}
if options.MapSortMode != codec.MapSortMode_None {
// Collect map entries, then sort by key
type entry struct {
key string
value datamodel.Node
}
entries := []entry{}
for itr := n.MapIterator(); !itr.Done(); {
k, v, err := itr.Next()
if err != nil {
return err
}
keyStr, err := k.AsString()
if err != nil {
return err
}
entries = append(entries, entry{keyStr, v})
}
if len(entries) != expectedLength {
return fmt.Errorf("map Length() does not match number of MapIterator() entries")
}
// Apply the desired sort function.
switch options.MapSortMode {
case codec.MapSortMode_Lexical:
sort.Slice(entries, func(i, j int) bool {
return entries[i].key < entries[j].key
})
case codec.MapSortMode_RFC7049:
sort.Slice(entries, func(i, j int) bool {
// RFC7049 style sort as per DAG-CBOR spec
li, lj := len(entries[i].key), len(entries[j].key)
if li == lj {
return entries[i].key < entries[j].key
}
return li < lj
})
}
// Emit map contents (and recurse).
for _, e := range entries {
tk.Type = tok.TString
tk.Str = e.key
if _, err := sink.Step(tk); err != nil {
return err
}
if err := marshal(e.value, tk, sink, options); err != nil {
return err
}
}
} else { // no sorting
// Emit map contents (and recurse).
var entryCount int
for itr := n.MapIterator(); !itr.Done(); {
k, v, err := itr.Next()
if err != nil {
return err
}
entryCount++
tk.Type = tok.TString
tk.Str, err = k.AsString()
if err != nil {
return err
}
if _, err := sink.Step(tk); err != nil {
return err
}
if err := marshal(v, tk, sink, options); err != nil {
return err
}
}
if entryCount != expectedLength {
return fmt.Errorf("map Length() does not match number of MapIterator() entries")
}
}
// Emit map close.
tk.Type = tok.TMapClose
_, err := sink.Step(tk)
return err
}
// EncodedLength will calculate the length in bytes that the encoded form of the
// provided Node will occupy.
//
// Note that this function requires a full walk of the Node's graph, which may
// not necessarily be a trivial cost and will incur some allocations. Using this
// method to calculate buffers to pre-allocate may not result in performance
// gains, but rather incur an overall cost. Use with care.
func EncodedLength(n datamodel.Node) (int64, error) {
switch n.Kind() {
case datamodel.Kind_Invalid:
return 0, fmt.Errorf("cannot traverse a node that is absent")
case datamodel.Kind_Null:
return 1, nil // 0xf6
case datamodel.Kind_Map:
length := uintLength(uint64(n.Length())) // length prefixed major 5
for itr := n.MapIterator(); !itr.Done(); {
k, v, err := itr.Next()
if err != nil {
return 0, err
}
keyLength, err := EncodedLength(k)
if err != nil {
return 0, err
}
length += keyLength
valueLength, err := EncodedLength(v)
if err != nil {
return 0, err
}
length += valueLength
}
return length, nil
case datamodel.Kind_List:
nl := n.Length()
length := uintLength(uint64(nl)) // length prefixed major 4
for i := int64(0); i < nl; i++ {
v, err := n.LookupByIndex(i)
if err != nil {
return 0, err
}
innerLength, err := EncodedLength(v)
if err != nil {
return 0, err
}
length += innerLength
}
return length, nil
case datamodel.Kind_Bool:
return 1, nil // 0xf4 or 0xf5
case datamodel.Kind_Int:
v, err := n.AsInt()
if err != nil {
return 0, err
}
if v < 0 {
v = -v - 1 // negint is stored as one less than actual
}
return uintLength(uint64(v)), nil // major 0 or 1, as small as possible
case datamodel.Kind_Float:
return 9, nil // always major 7 and 64-bit float
case datamodel.Kind_String:
v, err := n.AsString()
if err != nil {
return 0, err
}
return uintLength(uint64(len(v))) + int64(len(v)), nil // length prefixed major 3
case datamodel.Kind_Bytes:
v, err := n.AsBytes()
if err != nil {
return 0, err
}
return uintLength(uint64(len(v))) + int64(len(v)), nil // length prefixed major 2
case datamodel.Kind_Link:
v, err := n.AsLink()
if err != nil {
return 0, err
}
switch lnk := v.(type) {
case cidlink.Link:
length := int64(2) // tag,42: 0xd82a
bl := int64(len(lnk.Bytes())) + 1 // additional 0x00 in front of the CID bytes
length += uintLength(uint64(bl)) + bl // length prefixed major 2
return length, err
default:
return 0, fmt.Errorf("schemafree link emission only supported by this codec for CID type links")
}
default:
panic("unreachable")
}
}
// Calculate how many bytes an integer, and therefore also the leading bytes of
// a length-prefixed token. CBOR will pack it up into the smallest possible
// uint representation, even merging it with the major if it's <=23.
type boundaryLength struct {
upperBound uint64
length int64
}
var lengthBoundaries = []boundaryLength{
{24, 1}, // packed major|minor
{256, 2}, // major, 8-bit length
{65536, 3}, // major, 16-bit length
{4294967296, 5}, // major, 32-bit length
{0, 9}, // major, 64-bit length
}
func uintLength(ii uint64) int64 {
for _, lb := range lengthBoundaries {
if ii < lb.upperBound {
return lb.length
}
}
// maximum number of bytes to pack this int
// if this int is used as a length prefix for a map, list, string or bytes
// then we likely have a very bad Node that shouldn't be encoded, but the
// encoder may raise problems with that if the memory allocator doesn't first.
return lengthBoundaries[len(lengthBoundaries)-1].length
}

View File

@@ -0,0 +1,48 @@
package dagcbor
import (
"io"
"github.com/ipld/go-ipld-prime/codec"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/multicodec"
)
var (
_ codec.Decoder = Decode
_ codec.Encoder = Encode
)
func init() {
multicodec.RegisterEncoder(0x71, Encode)
multicodec.RegisterDecoder(0x71, Decode)
}
// Decode deserializes data from the given io.Reader and feeds it into the given datamodel.NodeAssembler.
// Decode fits the codec.Decoder function interface.
//
// A similar function is available on DecodeOptions type if you would like to customize any of the decoding details.
// This function uses the defaults for the dag-cbor codec
// (meaning: links (indicated by tag 42) are decoded).
//
// This is the function that will be registered in the default multicodec registry during package init time.
func Decode(na datamodel.NodeAssembler, r io.Reader) error {
return DecodeOptions{
AllowLinks: true,
}.Decode(na, r)
}
// Encode walks the given datamodel.Node and serializes it to the given io.Writer.
// Encode fits the codec.Encoder function interface.
//
// A similar function is available on EncodeOptions type if you would like to customize any of the encoding details.
// This function uses the defaults for the dag-cbor codec
// (meaning: links are encoded, and map keys are sorted (with RFC7049 ordering!) during encode).
//
// This is the function that will be registered in the default multicodec registry during package init time.
func Encode(n datamodel.Node, w io.Writer) error {
return EncodeOptions{
AllowLinks: true,
MapSortMode: codec.MapSortMode_RFC7049,
}.Encode(n, w)
}

View File

@@ -0,0 +1,307 @@
package dagcbor
import (
"errors"
"fmt"
"io"
"math"
cid "github.com/ipfs/go-cid"
"github.com/polydawn/refmt/cbor"
"github.com/polydawn/refmt/shared"
"github.com/polydawn/refmt/tok"
"github.com/ipld/go-ipld-prime/datamodel"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipld/go-ipld-prime/node/basicnode"
)
var (
ErrInvalidMultibase = errors.New("invalid multibase on IPLD link")
ErrAllocationBudgetExceeded = errors.New("message structure demanded too many resources to process")
ErrTrailingBytes = errors.New("unexpected content after end of cbor object")
)
const (
mapEntryGasScore = 8
listEntryGasScore = 4
)
// This file should be identical to the general feature in the parent package,
// except for the `case tok.TBytes` block,
// which has dag-cbor's special sauce for detecting schemafree links.
// DecodeOptions can be used to customize the behavior of a decoding function.
// The Decode method on this struct fits the codec.Decoder function interface.
type DecodeOptions struct {
// If true, parse DAG-CBOR tag(42) as Link nodes, otherwise reject them
AllowLinks bool
// TODO: ExperimentalDeterminism enforces map key order, but not the other parts
// of the spec such as integers or floats. See the fuzz failures spotted in
// https://github.com/ipld/go-ipld-prime/pull/389.
// When we're done implementing strictness, deprecate the option in favor of
// StrictDeterminism, but keep accepting both for backwards compatibility.
// ExperimentalDeterminism requires decoded DAG-CBOR bytes to be canonical as per
// the spec. For example, this means that integers and floats be encoded in
// a particular way, and map keys be sorted.
//
// The decoder does not enforce this requirement by default, as the codec
// was originally implemented without these rules. Because of that, there's
// a significant amount of published data that isn't canonical but should
// still decode with the default settings for backwards compatibility.
//
// Note that this option is experimental as it only implements partial strictness.
ExperimentalDeterminism bool
// If true, the decoder stops reading from the stream at the end of a full,
// valid CBOR object. This may be useful for parsing a stream of undelimited
// CBOR objects.
// As per standard IPLD behavior, in the default mode the parser considers the
// entire block to be part of the CBOR object and will error if there is
// extraneous data after the end of the object.
DontParseBeyondEnd bool
}
// Decode deserializes data from the given io.Reader and feeds it into the given datamodel.NodeAssembler.
// Decode fits the codec.Decoder function interface.
//
// The behavior of the decoder can be customized by setting fields in the DecodeOptions struct before calling this method.
func (cfg DecodeOptions) Decode(na datamodel.NodeAssembler, r io.Reader) error {
// Probe for a builtin fast path. Shortcut to that if possible.
type detectFastPath interface {
DecodeDagCbor(io.Reader) error
}
if na2, ok := na.(detectFastPath); ok {
return na2.DecodeDagCbor(r)
}
// Okay, generic builder path.
err := Unmarshal(na, cbor.NewDecoder(cbor.DecodeOptions{
CoerceUndefToNull: true,
}, r), cfg)
if err != nil {
return err
}
if cfg.DontParseBeyondEnd {
return nil
}
var buf [1]byte
_, err = io.ReadFull(r, buf[:])
switch err {
case io.EOF:
return nil
case nil:
return ErrTrailingBytes
default:
return err
}
}
// Future work: we would like to remove the Unmarshal function,
// and in particular, stop seeing types from refmt (like shared.TokenSource) be visible.
// Right now, some kinds of configuration (e.g. for whitespace and prettyprint) are only available through interacting with the refmt types;
// we should improve our API so that this can be done with only our own types in this package.
// Unmarshal is a deprecated function.
// Please consider switching to DecodeOptions.Decode instead.
func Unmarshal(na datamodel.NodeAssembler, tokSrc shared.TokenSource, options DecodeOptions) error {
// Have a gas budget, which will be decremented as we allocate memory, and an error returned when execeeded (or about to be exceeded).
// This is a DoS defense mechanism.
// It's *roughly* in units of bytes (but only very, VERY roughly) -- it also treats words as 1 in many cases.
// FUTURE: this ought be configurable somehow. (How, and at what granularity though?)
var gas int64 = 1048576 * 10
return unmarshal1(na, tokSrc, &gas, options)
}
func unmarshal1(na datamodel.NodeAssembler, tokSrc shared.TokenSource, gas *int64, options DecodeOptions) error {
var tk tok.Token
done, err := tokSrc.Step(&tk)
if err == io.EOF {
return io.ErrUnexpectedEOF
}
if err != nil {
return err
}
if done && !tk.Type.IsValue() && tk.Type != tok.TNull {
return fmt.Errorf("unexpected eof")
}
return unmarshal2(na, tokSrc, &tk, gas, options)
}
// starts with the first token already primed. Necessary to get recursion
//
// to flow right without a peek+unpeek system.
func unmarshal2(na datamodel.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token, gas *int64, options DecodeOptions) error {
// 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 := int64(tk.Length)
allocLen := int64(tk.Length)
if tk.Length == -1 {
expectLen = math.MaxInt64
allocLen = 0
} else {
if *gas-allocLen < 0 { // halt early if this will clearly demand too many resources
return ErrAllocationBudgetExceeded
}
}
ma, err := na.BeginMap(allocLen)
if err != nil {
return err
}
var observedLen int64
lastKey := ""
for {
_, err := tokSrc.Step(tk)
if err != nil {
return err
}
switch tk.Type {
case tok.TMapClose:
if expectLen != math.MaxInt64 && observedLen != expectLen {
return fmt.Errorf("unexpected mapClose before declared length")
}
return ma.Finish()
case tok.TString:
*gas -= int64(len(tk.Str) + mapEntryGasScore)
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
// continue
default:
return fmt.Errorf("unexpected %s token while expecting map key", tk.Type)
}
observedLen++
if observedLen > expectLen {
return fmt.Errorf("unexpected continuation of map elements beyond declared length")
}
if observedLen > 1 && options.ExperimentalDeterminism {
if len(lastKey) > len(tk.Str) || lastKey > tk.Str {
return fmt.Errorf("map key %q is not after %q as per RFC7049", tk.Str, lastKey)
}
}
lastKey = tk.Str
mva, err := ma.AssembleEntry(tk.Str)
if err != nil { // return in error if the key was rejected
return err
}
err = unmarshal1(mva, tokSrc, gas, options)
if err != nil { // return in error if some part of the recursion errored
return err
}
}
case tok.TMapClose:
return fmt.Errorf("unexpected mapClose token")
case tok.TArrOpen:
expectLen := int64(tk.Length)
allocLen := int64(tk.Length)
if tk.Length == -1 {
expectLen = math.MaxInt64
allocLen = 0
} else {
if *gas-allocLen < 0 { // halt early if this will clearly demand too many resources
return ErrAllocationBudgetExceeded
}
}
la, err := na.BeginList(allocLen)
if err != nil {
return err
}
var observedLen int64
for {
_, err := tokSrc.Step(tk)
if err != nil {
return err
}
switch tk.Type {
case tok.TArrClose:
if expectLen != math.MaxInt64 && observedLen != expectLen {
return fmt.Errorf("unexpected arrClose before declared length")
}
return la.Finish()
default:
*gas -= listEntryGasScore
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
observedLen++
if observedLen > expectLen {
return fmt.Errorf("unexpected continuation of array elements beyond declared length")
}
err := unmarshal2(la.AssembleValue(), tokSrc, tk, gas, options)
if err != nil { // return in error if some part of the recursion errored
return err
}
}
}
case tok.TArrClose:
return fmt.Errorf("unexpected arrClose token")
case tok.TNull:
return na.AssignNull()
case tok.TString:
*gas -= int64(len(tk.Str))
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
return na.AssignString(tk.Str)
case tok.TBytes:
*gas -= int64(len(tk.Bytes))
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
if !tk.Tagged {
return na.AssignBytes(tk.Bytes)
}
switch tk.Tag {
case linkTag:
if !options.AllowLinks {
return fmt.Errorf("unhandled cbor tag %d", tk.Tag)
}
if len(tk.Bytes) < 1 || tk.Bytes[0] != 0 {
return ErrInvalidMultibase
}
elCid, err := cid.Cast(tk.Bytes[1:])
if err != nil {
return err
}
return na.AssignLink(cidlink.Link{Cid: elCid})
default:
return fmt.Errorf("unhandled cbor tag %d", tk.Tag)
}
case tok.TBool:
*gas -= 1
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
return na.AssignBool(tk.Bool)
case tok.TInt:
*gas -= 1
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
return na.AssignInt(tk.Int)
case tok.TUint:
*gas -= 1
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
// note that this pushes any overflow errors up the stack when AsInt() may
// be called on a UintNode that is too large to cast to an int64
if tk.Uint > math.MaxInt64 {
return na.AssignNode(basicnode.NewUint(tk.Uint))
}
return na.AssignInt(int64(tk.Uint))
case tok.TFloat64:
*gas -= 1
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
return na.AssignFloat(tk.Float64)
default:
panic("unreachable")
}
}

170
vendor/github.com/ipld/go-ipld-prime/codecHelpers.go generated vendored Normal file
View File

@@ -0,0 +1,170 @@
package ipld
import (
"bytes"
"io"
"reflect"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/ipld/go-ipld-prime/node/bindnode"
"github.com/ipld/go-ipld-prime/schema"
)
// Encode serializes the given Node using the given Encoder function,
// returning the serialized data or an error.
//
// The exact result data will depend the node content and on the encoder function,
// but for example, using a json codec on a node with kind map will produce
// a result starting in `{`, etc.
//
// Encode will automatically switch to encoding the representation form of the Node,
// if it discovers the Node matches the schema.TypedNode interface.
// This is probably what you want, in most cases;
// if this is not desired, you can use the underlaying functions directly
// (just look at the source of this function for an example of how!).
//
// If you would like this operation, but applied directly to a golang type instead of a Node,
// look to the Marshal function.
func Encode(n Node, encFn Encoder) ([]byte, error) {
var buf bytes.Buffer
err := EncodeStreaming(&buf, n, encFn)
return buf.Bytes(), err
}
// EncodeStreaming is like Encode, but emits output to an io.Writer.
func EncodeStreaming(wr io.Writer, n Node, encFn Encoder) error {
if tn, ok := n.(schema.TypedNode); ok {
n = tn.Representation()
}
return encFn(n, wr)
}
// Decode parses the given bytes into a Node using the given Decoder function,
// returning a new Node or an error.
//
// The new Node that is returned will be the implementation from the node/basicnode package.
// This implementation of Node will work for storing any kind of data,
// but note that because it is general, it is also not necessarily optimized.
// If you want more control over what kind of Node implementation (and thus memory layout) is used,
// or want to use features like IPLD Schemas (which can be engaged by using a schema.TypedPrototype),
// then look to the DecodeUsingPrototype family of functions,
// which accept more parameters in order to give you that kind of control.
//
// If you would like this operation, but applied directly to a golang type instead of a Node,
// look to the Unmarshal function.
func Decode(b []byte, decFn Decoder) (Node, error) {
return DecodeUsingPrototype(b, decFn, basicnode.Prototype.Any)
}
// DecodeStreaming is like Decode, but works on an io.Reader for input.
func DecodeStreaming(r io.Reader, decFn Decoder) (Node, error) {
return DecodeStreamingUsingPrototype(r, decFn, basicnode.Prototype.Any)
}
// DecodeUsingPrototype is like Decode, but with a NodePrototype parameter,
// which gives you control over the Node type you'll receive,
// and thus control over the memory layout, and ability to use advanced features like schemas.
// (Decode is simply this function, but hardcoded to use basicnode.Prototype.Any.)
//
// DecodeUsingPrototype internally creates a NodeBuilder, and thows it away when done.
// If building a high performance system, and creating data of the same shape repeatedly,
// you may wish to use NodeBuilder directly, so that you can control and avoid these allocations.
//
// For symmetry with the behavior of Encode, DecodeUsingPrototype will automatically
// switch to using the representation form of the node for decoding
// if it discovers the NodePrototype matches the schema.TypedPrototype interface.
// This is probably what you want, in most cases;
// if this is not desired, you can use the underlaying functions directly
// (just look at the source of this function for an example of how!).
func DecodeUsingPrototype(b []byte, decFn Decoder, np NodePrototype) (Node, error) {
return DecodeStreamingUsingPrototype(bytes.NewReader(b), decFn, np)
}
// DecodeStreamingUsingPrototype is like DecodeUsingPrototype, but works on an io.Reader for input.
func DecodeStreamingUsingPrototype(r io.Reader, decFn Decoder, np NodePrototype) (Node, error) {
if tnp, ok := np.(schema.TypedPrototype); ok {
np = tnp.Representation()
}
nb := np.NewBuilder()
if err := decFn(nb, r); err != nil {
return nil, err
}
return nb.Build(), nil
}
// Marshal accepts a pointer to a Go value and an IPLD schema type,
// and encodes the representation form of that data (which may be configured with the schema!)
// using the given Encoder function.
//
// Marshal uses the node/bindnode subsystem.
// See the documentation in that package for more details about its workings.
// Please note that this subsystem is relatively experimental at this time.
//
// The schema.Type parameter is optional, and can be nil.
// If given, it controls what kind of schema.Type (and what kind of representation strategy!)
// to use when processing the data.
// If absent, a default schema.Type will be inferred based on the golang type
// (so, a struct in go will be inferred to have a schema with a similar struct, and the default representation strategy (e.g. map), etc).
// Note that not all features of IPLD Schemas can be inferred from golang types alone.
// For example, to use union types, the schema parameter will be required.
// Similarly, to use most kinds of non-default representation strategy, the schema parameter is needed in order to convey that intention.
func Marshal(encFn Encoder, bind interface{}, typ schema.Type) ([]byte, error) {
n := bindnode.Wrap(bind, typ)
return Encode(n.Representation(), encFn)
}
// MarshalStreaming is like Marshal, but emits output to an io.Writer.
func MarshalStreaming(wr io.Writer, encFn Encoder, bind interface{}, typ schema.Type) error {
n := bindnode.Wrap(bind, typ)
return EncodeStreaming(wr, n.Representation(), encFn)
}
// Unmarshal accepts a pointer to a Go value and an IPLD schema type,
// and fills the value with data by decoding into it with the given Decoder function.
//
// Unmarshal uses the node/bindnode subsystem.
// See the documentation in that package for more details about its workings.
// Please note that this subsystem is relatively experimental at this time.
//
// The schema.Type parameter is optional, and can be nil.
// If given, it controls what kind of schema.Type (and what kind of representation strategy!)
// to use when processing the data.
// If absent, a default schema.Type will be inferred based on the golang type
// (so, a struct in go will be inferred to have a schema with a similar struct, and the default representation strategy (e.g. map), etc).
// Note that not all features of IPLD Schemas can be inferred from golang types alone.
// For example, to use union types, the schema parameter will be required.
// Similarly, to use most kinds of non-default representation strategy, the schema parameter is needed in order to convey that intention.
//
// In contrast to some other unmarshal conventions common in golang,
// notice that we also return a Node value.
// This Node points to the same data as the value you handed in as the bind parameter,
// while making it available to read and iterate and handle as a ipld datamodel.Node.
// If you don't need that interface, or intend to re-bind it later, you can discard that value.
//
// The 'bind' parameter may be nil.
// In that case, the type of the nil is still used to infer what kind of value to return,
// and a Node will still be returned based on that type.
// bindnode.Unwrap can be used on that Node and will still return something
// of the same golang type as the typed nil that was given as the 'bind' parameter.
func Unmarshal(b []byte, decFn Decoder, bind interface{}, typ schema.Type) (Node, error) {
return UnmarshalStreaming(bytes.NewReader(b), decFn, bind, typ)
}
// UnmarshalStreaming is like Unmarshal, but works on an io.Reader for input.
func UnmarshalStreaming(r io.Reader, decFn Decoder, bind interface{}, typ schema.Type) (Node, error) {
// Decode is fairly straightforward.
np := bindnode.Prototype(bind, typ)
n, err := DecodeStreamingUsingPrototype(r, decFn, np.Representation())
if err != nil {
return nil, err
}
// ... but our approach above allocated new memory, and we have to copy it back out.
// In the future, the bindnode API could be improved to make this easier.
if !reflect.ValueOf(bind).IsNil() {
reflect.ValueOf(bind).Elem().Set(reflect.ValueOf(bindnode.Unwrap(n)).Elem())
}
// ... and we also have to re-bind a new node to the 'bind' value,
// because probably the user will be surprised if mutating 'bind' doesn't affect the Node later.
n = bindnode.Wrap(bind, typ)
return n, err
}

114
vendor/github.com/ipld/go-ipld-prime/datamodel.go generated vendored Normal file
View File

@@ -0,0 +1,114 @@
package ipld
import (
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/linking"
"github.com/ipld/go-ipld-prime/schema"
)
type (
Kind = datamodel.Kind
Node = datamodel.Node
NodeAssembler = datamodel.NodeAssembler
NodeBuilder = datamodel.NodeBuilder
NodePrototype = datamodel.NodePrototype
MapIterator = datamodel.MapIterator
MapAssembler = datamodel.MapAssembler
ListIterator = datamodel.ListIterator
ListAssembler = datamodel.ListAssembler
Link = datamodel.Link
LinkPrototype = datamodel.LinkPrototype
Path = datamodel.Path
PathSegment = datamodel.PathSegment
)
var (
Null = datamodel.Null
Absent = datamodel.Absent
)
const (
Kind_Invalid = datamodel.Kind_Invalid
Kind_Map = datamodel.Kind_Map
Kind_List = datamodel.Kind_List
Kind_Null = datamodel.Kind_Null
Kind_Bool = datamodel.Kind_Bool
Kind_Int = datamodel.Kind_Int
Kind_Float = datamodel.Kind_Float
Kind_String = datamodel.Kind_String
Kind_Bytes = datamodel.Kind_Bytes
Kind_Link = datamodel.Kind_Link
)
// Future: These aliases for the `KindSet_*` values may be dropped someday.
// I don't think they're very important to have cluttering up namespace here.
// They're included for a brief transitional period, largely for the sake of codegen things which have referred to them, but may disappear in the future.
var (
KindSet_Recursive = datamodel.KindSet_Recursive
KindSet_Scalar = datamodel.KindSet_Scalar
KindSet_JustMap = datamodel.KindSet_JustMap
KindSet_JustList = datamodel.KindSet_JustList
KindSet_JustNull = datamodel.KindSet_JustNull
KindSet_JustBool = datamodel.KindSet_JustBool
KindSet_JustInt = datamodel.KindSet_JustInt
KindSet_JustFloat = datamodel.KindSet_JustFloat
KindSet_JustString = datamodel.KindSet_JustString
KindSet_JustBytes = datamodel.KindSet_JustBytes
KindSet_JustLink = datamodel.KindSet_JustLink
)
// Future: These error type aliases may be dropped someday.
// Being able to see them as having more than one package name is not helpful to clarity.
// They are left here for now for a brief transitional period, because it was relatively easy to do so.
type (
ErrWrongKind = datamodel.ErrWrongKind
ErrNotExists = datamodel.ErrNotExists
ErrRepeatedMapKey = datamodel.ErrRepeatedMapKey
ErrInvalidSegmentForList = datamodel.ErrInvalidSegmentForList
ErrIteratorOverread = datamodel.ErrIteratorOverread
ErrInvalidKey = schema.ErrInvalidKey
ErrMissingRequiredField = schema.ErrMissingRequiredField
ErrHashMismatch = linking.ErrHashMismatch
)
// Future: a bunch of these alias methods for path creation may be dropped someday.
// They don't hurt anything, but I don't think they add much clarity either, vs the amount of namespace noise they create;
// many of the high level convenience functions we add here in the root package will probably refer to datamodel.Path, and that should be sufficient to create clarity for new users for where to look for more on pathing.
// They are here for now for a transitional period, but may eventually be revisited and perhaps removed.
// NewPath is an alias for datamodel.NewPath.
//
// Pathing is a concept defined in the data model layer of IPLD.
func NewPath(segments []PathSegment) Path {
return datamodel.NewPath(segments)
}
// ParsePath is an alias for datamodel.ParsePath.
//
// Pathing is a concept defined in the data model layer of IPLD.
func ParsePath(pth string) Path {
return datamodel.ParsePath(pth)
}
// ParsePathSegment is an alias for datamodel.ParsePathSegment.
//
// Pathing is a concept defined in the data model layer of IPLD.
func ParsePathSegment(s string) PathSegment {
return datamodel.ParsePathSegment(s)
}
// PathSegmentOfString is an alias for datamodel.PathSegmentOfString.
//
// Pathing is a concept defined in the data model layer of IPLD.
func PathSegmentOfString(s string) PathSegment {
return datamodel.PathSegmentOfString(s)
}
// PathSegmentOfInt is an alias for datamodel.PathSegmentOfInt.
//
// Pathing is a concept defined in the data model layer of IPLD.
func PathSegmentOfInt(i int64) PathSegment {
return datamodel.PathSegmentOfInt(i)
}

113
vendor/github.com/ipld/go-ipld-prime/datamodel/copy.go generated vendored Normal file
View File

@@ -0,0 +1,113 @@
package datamodel
import (
"errors"
"fmt"
)
// Copy does an explicit shallow copy of a Node's data into a NodeAssembler.
//
// This can be used to flip data from one memory layout to another
// (for example, from basicnode to using using bindnode,
// or to codegenerated node implementations,
// or to or from ADL nodes, etc).
//
// The copy is implemented by ranging over the contents if it's a recursive kind,
// and for each of them, using `AssignNode` on the child values;
// for scalars, it's just calling the appropriate `Assign*` method.
//
// Many NodeAssembler implementations use this as a fallback behavior in their
// `AssignNode` method (that is, they call to this function after all other special
// faster shortcuts they might prefer to employ, such as direct struct copying
// if they share internal memory layouts, etc, have been tried already).
func Copy(n Node, na NodeAssembler) error {
if n == nil {
return errors.New("cannot copy a nil node")
}
switch n.Kind() {
case Kind_Null:
if n.IsAbsent() {
return errors.New("copying an absent node makes no sense")
}
return na.AssignNull()
case Kind_Bool:
v, err := n.AsBool()
if err != nil {
return fmt.Errorf("node violated contract: promised to be %v kind, but AsBool method returned %w", n.Kind(), err)
}
return na.AssignBool(v)
case Kind_Int:
v, err := n.AsInt()
if err != nil {
return fmt.Errorf("node violated contract: promised to be %v kind, but AsInt method returned %w", n.Kind(), err)
}
return na.AssignInt(v)
case Kind_Float:
v, err := n.AsFloat()
if err != nil {
return fmt.Errorf("node violated contract: promised to be %v kind, but AsFloat method returned %w", n.Kind(), err)
}
return na.AssignFloat(v)
case Kind_String:
v, err := n.AsString()
if err != nil {
return fmt.Errorf("node violated contract: promised to be %v kind, but AsString method returned %w", n.Kind(), err)
}
return na.AssignString(v)
case Kind_Bytes:
v, err := n.AsBytes()
if err != nil {
return fmt.Errorf("node violated contract: promised to be %v kind, but AsBytes method returned %w", n.Kind(), err)
}
return na.AssignBytes(v)
case Kind_Link:
v, err := n.AsLink()
if err != nil {
return fmt.Errorf("node violated contract: promised to be %v kind, but AsLink method returned %w", n.Kind(), err)
}
return na.AssignLink(v)
case Kind_Map:
ma, err := na.BeginMap(n.Length())
if err != nil {
return err
}
itr := n.MapIterator()
for !itr.Done() {
k, v, err := itr.Next()
if err != nil {
return err
}
if v.IsAbsent() {
continue
}
if err := ma.AssembleKey().AssignNode(k); err != nil {
return err
}
if err := ma.AssembleValue().AssignNode(v); err != nil {
return err
}
}
return ma.Finish()
case Kind_List:
la, err := na.BeginList(n.Length())
if err != nil {
return err
}
itr := n.ListIterator()
for !itr.Done() {
_, v, err := itr.Next()
if err != nil {
return err
}
if v.IsAbsent() {
continue
}
if err := la.AssembleValue().AssignNode(v); err != nil {
return err
}
}
return la.Finish()
default:
return fmt.Errorf("node has invalid kind %v", n.Kind())
}
}

13
vendor/github.com/ipld/go-ipld-prime/datamodel/doc.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
// The datamodel package defines the most essential interfaces for describing IPLD Data --
// such as Node, NodePrototype, NodeBuilder, Link, and Path.
//
// Note that since interfaces in this package are the core of the library,
// choices made here maximize correctness and performance -- these choices
// are *not* always the choices that would maximize ergonomics.
// (Ergonomics can come on top; performance generally can't.)
// You'll want to check out other packages for functions with more ergonomics;
// for example, 'fluent' and its subpackages provide lots of ways to work with data;
// 'traversal' provides some ergonomic features for walking around data graphs;
// any use of schemas will provide a bunch of useful data validation options;
// or you can make your own function decorators that do what *you* need.
package datamodel

156
vendor/github.com/ipld/go-ipld-prime/datamodel/equal.go generated vendored Normal file
View File

@@ -0,0 +1,156 @@
package datamodel
// DeepEqual reports whether x and y are "deeply equal" as IPLD nodes.
// This is similar to reflect.DeepEqual, but based around the Node interface.
//
// Two nodes must have the same kind to be deeply equal.
// If either node has the invalid kind, the nodes are not deeply equal.
//
// Two nodes of scalar kinds (null, bool, int, float, string, bytes, link)
// are deeply equal if their Go values, as returned by AsKind methods, are equal as
// per Go's == comparison operator.
//
// Note that Links are compared in a shallow way, without being followed.
// This will generally be enough, as it's rare to have two different links to the
// same IPLD data by using a different codec or multihash type.
//
// Two nodes of recursive kinds (map, list)
// must have the same length to be deeply equal.
// Their elements, as reported by iterators, must be deeply equal.
// The elements are compared in the iterator's order,
// meaning two maps sorting the same keys differently might not be equal.
//
// Note that this function panics if either Node returns an error.
// We only call valid methods for each Kind,
// so an error should only happen if a Node implementation breaks that contract.
// It is generally not recommended to call DeepEqual on ADL nodes.
func DeepEqual(x, y Node) bool {
if x == nil || y == nil {
return x == y
}
xk, yk := x.Kind(), y.Kind()
if xk != yk {
return false
}
switch xk {
// Scalar kinds.
case Kind_Null:
return x.IsNull() == y.IsNull()
case Kind_Bool:
xv, err := x.AsBool()
if err != nil {
panic(err)
}
yv, err := y.AsBool()
if err != nil {
panic(err)
}
return xv == yv
case Kind_Int:
xv, err := x.AsInt()
if err != nil {
panic(err)
}
yv, err := y.AsInt()
if err != nil {
panic(err)
}
return xv == yv
case Kind_Float:
xv, err := x.AsFloat()
if err != nil {
panic(err)
}
yv, err := y.AsFloat()
if err != nil {
panic(err)
}
return xv == yv
case Kind_String:
xv, err := x.AsString()
if err != nil {
panic(err)
}
yv, err := y.AsString()
if err != nil {
panic(err)
}
return xv == yv
case Kind_Bytes:
xv, err := x.AsBytes()
if err != nil {
panic(err)
}
yv, err := y.AsBytes()
if err != nil {
panic(err)
}
return string(xv) == string(yv)
case Kind_Link:
xv, err := x.AsLink()
if err != nil {
panic(err)
}
yv, err := y.AsLink()
if err != nil {
panic(err)
}
// Links are just compared via ==.
// This requires the types to exactly match,
// and the values to be equal as per == too.
// This will generally work,
// as ipld-prime assumes link types to be consistent.
return xv == yv
// Recursive kinds.
case Kind_Map:
if x.Length() != y.Length() {
return false
}
xitr := x.MapIterator()
yitr := y.MapIterator()
for !xitr.Done() && !yitr.Done() {
xkey, xval, err := xitr.Next()
if err != nil {
panic(err)
}
ykey, yval, err := yitr.Next()
if err != nil {
panic(err)
}
if !DeepEqual(xkey, ykey) {
return false
}
if !DeepEqual(xval, yval) {
return false
}
}
return true
case Kind_List:
if x.Length() != y.Length() {
return false
}
xitr := x.ListIterator()
yitr := y.ListIterator()
for !xitr.Done() && !yitr.Done() {
_, xval, err := xitr.Next()
if err != nil {
panic(err)
}
_, yval, err := yitr.Next()
if err != nil {
panic(err)
}
if !DeepEqual(xval, yval) {
return false
}
}
return true
// As per the docs, other kinds such as Invalid are not deeply equal.
default:
return false
}
}

View File

@@ -0,0 +1,107 @@
package datamodel
import (
"fmt"
)
// ErrWrongKind may be returned from functions on the Node interface when
// a method is invoked which doesn't make sense for the Kind that node
// concretely contains.
//
// For example, calling AsString on a map will return ErrWrongKind.
// Calling Lookup on an int will similarly return ErrWrongKind.
type ErrWrongKind struct {
// TypeName may optionally indicate the named type of a node the function
// was called on (if the node was typed!), or, may be the empty string.
TypeName string
// MethodName is literally the string for the operation attempted, e.g.
// "AsString".
//
// For methods on nodebuilders, we say e.g. "NodeBuilder.CreateMap".
MethodName string
// ApprorpriateKind describes which Kinds the erroring method would
// make sense for.
AppropriateKind KindSet
// ActualKind describes the Kind of the node the method was called on.
//
// In the case of typed nodes, this will typically refer to the 'natural'
// data-model kind for such a type (e.g., structs will say 'map' here).
ActualKind Kind
// TODO: it may be desirable for this error to be able to describe the schema typekind, too, if applicable.
// Of course this presents certain package import graph problems. Solution to this that maximizes user usability is unclear.
}
func (e ErrWrongKind) Error() string {
if e.TypeName == "" {
return fmt.Sprintf("func called on wrong kind: %q called on a %s node, but only makes sense on %s", e.MethodName, e.ActualKind, e.AppropriateKind)
} else {
return fmt.Sprintf("func called on wrong kind: %q called on a %s node (kind: %s), but only makes sense on %s", e.MethodName, e.TypeName, e.ActualKind, e.AppropriateKind)
}
}
// TODO: revisit the claim below about ErrNoSuchField. I think we moved back away from that, or want to.
// ErrNotExists may be returned from the lookup functions of the Node interface
// to indicate a missing value.
//
// 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
// simply absent in some data).
type ErrNotExists struct {
Segment PathSegment
}
func (e ErrNotExists) Error() string {
return fmt.Sprintf("key not found: %q", e.Segment)
}
// ErrRepeatedMapKey is an error indicating that a key was inserted
// into a map that already contains that key.
//
// This error may be returned by any methods that add data to a map --
// any of the methods on a NodeAssembler that was yielded by MapAssembler.AssignKey(),
// or from the MapAssembler.AssignDirectly() method.
type ErrRepeatedMapKey struct {
Key Node
}
func (e ErrRepeatedMapKey) Error() string {
return fmt.Sprintf("cannot repeat map key %q", e.Key)
}
// ErrInvalidSegmentForList is returned when using Node.LookupBySegment and the
// given PathSegment can't be applied to a list because it's unparsable as a number.
type ErrInvalidSegmentForList struct {
// TypeName may indicate the named type of a node the function was called on,
// or be empty string if working on untyped data.
TypeName string
// TroubleSegment is the segment we couldn't use.
TroubleSegment PathSegment
// Reason may explain more about why the PathSegment couldn't be used;
// in practice, it's probably a 'strconv.NumError'.
Reason error
}
func (e ErrInvalidSegmentForList) Error() string {
v := "invalid segment for lookup on a list"
if e.TypeName != "" {
v += " of type " + e.TypeName
}
return v + fmt.Sprintf(": %q: %s", e.TroubleSegment.s, e.Reason)
}
// ErrIteratorOverread is returned when calling 'Next' on a MapIterator or
// ListIterator when it is already done.
type ErrIteratorOverread struct{}
func (e ErrIteratorOverread) Error() string {
return "iterator overread"
}

90
vendor/github.com/ipld/go-ipld-prime/datamodel/kind.go generated vendored Normal file
View File

@@ -0,0 +1,90 @@
package datamodel
// Kind represents the primitive kind in the IPLD data model.
// All of these kinds map directly onto serializable data.
//
// Note that Kind contains the concept of "map", but not "struct"
// or "object" -- those are a concepts that could be introduced in a
// type system layers, but are *not* present in the data model layer,
// and therefore they aren't included in the Kind enum.
type Kind uint8
const (
Kind_Invalid Kind = 0
Kind_Map Kind = '{'
Kind_List Kind = '['
Kind_Null Kind = '0'
Kind_Bool Kind = 'b'
Kind_Int Kind = 'i'
Kind_Float Kind = 'f'
Kind_String Kind = 's'
Kind_Bytes Kind = 'x'
Kind_Link Kind = '/'
)
func (k Kind) String() string {
switch k {
case Kind_Invalid:
return "INVALID"
case Kind_Map:
return "map"
case Kind_List:
return "list"
case Kind_Null:
return "null"
case Kind_Bool:
return "bool"
case Kind_Int:
return "int"
case Kind_Float:
return "float"
case Kind_String:
return "string"
case Kind_Bytes:
return "bytes"
case Kind_Link:
return "link"
default:
panic("invalid enumeration value!")
}
}
// KindSet is a type with a few enumerated consts that are commonly used
// (mostly, in error messages).
type KindSet []Kind
var (
KindSet_Recursive = KindSet{Kind_Map, Kind_List}
KindSet_Scalar = KindSet{Kind_Null, Kind_Bool, Kind_Int, Kind_Float, Kind_String, Kind_Bytes, Kind_Link}
KindSet_JustMap = KindSet{Kind_Map}
KindSet_JustList = KindSet{Kind_List}
KindSet_JustNull = KindSet{Kind_Null}
KindSet_JustBool = KindSet{Kind_Bool}
KindSet_JustInt = KindSet{Kind_Int}
KindSet_JustFloat = KindSet{Kind_Float}
KindSet_JustString = KindSet{Kind_String}
KindSet_JustBytes = KindSet{Kind_Bytes}
KindSet_JustLink = KindSet{Kind_Link}
)
func (x KindSet) String() string {
if len(x) == 0 {
return "<empty KindSet>"
}
s := ""
for i := 0; i < len(x)-1; i++ {
s += x[i].String() + " or "
}
s += x[len(x)-1].String()
return s
}
func (x KindSet) Contains(e Kind) bool {
for _, v := range x {
if v == e {
return true
}
}
return false
}

68
vendor/github.com/ipld/go-ipld-prime/datamodel/link.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
package datamodel
// Link is a special kind of value in IPLD which can be "loaded" to access more nodes.
//
// Nodes can be a Link: "link" is one of the kinds in the IPLD Data Model;
// and accordingly there is an `ipld.Kind_Link` enum value, and Node has an `AsLink` method.
//
// Links are considered a scalar value in the IPLD Data Model,
// but when "loaded", the result can be any other IPLD kind:
// maps, lists, strings, etc.
//
// Link is an interface in the go-ipld implementation,
// but the most common instantiation of it comes from the `linking/cid` package,
// and represents CIDs (see https://github.com/multiformats/cid).
//
// The Link interface says very little by itself; it's generally necessary to
// use type assertions to unpack more specific forms of data.
// The only real contract is that the Link must be able to return a LinkPrototype,
// which must be able to produce new Link values of a similar form.
// (In practice: if you're familiar with CIDs: Link.Prototype is analogous to cid.Prefix.)
//
// The traversal package contains powerful features for walking through large graphs of Nodes
// while automatically loading and traversing links as the walk goes.
//
// Note that the Link interface should typically be inhabited by a struct or string, as opposed to a pointer.
// This is because Link is often desirable to be able to use as a golang map key,
// and in that context, pointers would not result in the desired behavior.
type Link interface {
// Prototype should return a LinkPrototype which carries the information
// to make more Link values similar to this one (but with different hashes).
Prototype() LinkPrototype
// String should return a reasonably human-readable debug-friendly representation the Link.
// There is no contract that requires that the string be able to be parsed back into a Link value,
// but the string should be unique (e.g. not elide any parts of the hash).
String() string
// Binary should return the densest possible encoding of the Link.
// The value need not be printable or human-readable;
// the golang string type is used for immutability and for ease of use as a map key.
// As with the String method, the returned value may not elide any parts of the hash.
//
// Note that there is still no contract that the returned value should be parsable back into a Link value;
// not even in the case of `lnk.Prototype().BuildLink(lnk.Binary()[:])`.
// This is because the value returned by this method may contain data that the LinkPrototype would also restate.
// (For a concrete example: if using CIDs, this method will return a binary string that includes
// the cid version indicator, the multicodec and multihash indicators, etc, in addition to the hash itself --
// whereas the LinkPrototype.BuildLink function still expects to receive only the hash itself alone.)
Binary() string
}
// LinkPrototype encapsulates any implementation details and parameters
// necessary for creating a Link, expect for the hash result itself.
//
// LinkPrototype, like Link, is an interface in go-ipld,
// but the most common instantiation of it comes from the `linking/cid` package,
// and represents CIDs (see https://github.com/multiformats/cid).
// If using CIDs as an implementation, LinkPrototype will encapsulate information
// like multihashType, multicodecType, and cidVersion, for example.
// (LinkPrototype is analogous to cid.Prefix.)
type LinkPrototype interface {
// BuildLink should return a new Link value based on the given hashsum.
// The hashsum argument should typically be a value returned from a
// https://golang.org/pkg/hash/#Hash.Sum call.
//
// The hashsum reference must not be retained (the caller is free to reuse it).
BuildLink(hashsum []byte) Link
}

327
vendor/github.com/ipld/go-ipld-prime/datamodel/node.go generated vendored Normal file
View File

@@ -0,0 +1,327 @@
package datamodel
import "io"
// Node represents a value in IPLD. Any point in a tree of data is a node:
// scalar values (like int64, string, etc) are nodes, and
// so are recursive values (like map and list).
//
// Nodes and kinds are described in the IPLD specs at
// https://github.com/ipld/specs/blob/master/data-model-layer/data-model.md .
//
// Methods on the Node interface cover the superset of all possible methods for
// all possible kinds -- but some methods only make sense for particular kinds,
// and thus will only make sense to call on values of the appropriate kind.
// (For example, 'Length' on an integer doesn't make sense,
// and 'AsInt' on a map certainly doesn't work either!)
// Use the Kind method to find out the kind of value before
// calling kind-specific methods.
// Individual method documentation state which kinds the method is valid for.
// (If you're familiar with the stdlib reflect package, you'll find
// the design of the Node interface very comparable to 'reflect.Value'.)
//
// The Node interface is read-only. All of the methods on the interface are
// for examining values, and implementations should be immutable.
// The companion interface, NodeBuilder, provides the matching writable
// methods, and should be use to create a (thence immutable) Node.
//
// Keeping Node immutable and separating mutation into NodeBuilder makes
// it possible to perform caching (or rather, memoization, since there's no
// such thing as cache invalidation for immutable systems) of computed
// properties of Node; use copy-on-write algorithms for memory efficiency;
// and to generally build pleasant APIs.
// Many library functions will rely on the immutability of Node (e.g.,
// assuming that pointer-equal nodes do not change in value over time),
// so any user-defined Node implementations should be careful to uphold
// the immutability contract.)
//
// There are many different concrete types which implement Node.
// The primary purpose of various node implementations is to organize
// memory in the program in different ways -- some in-memory layouts may
// be more optimal for some programs than others, and changing the Node
// (and NodeBuilder) implementations lets the programmer choose.
//
// For concrete implementations of Node, check out the "./node/" folder,
// and the packages within it.
// "node/basicnode" should probably be your first start; the Node and NodeBuilder
// implementations in that package work for any data.
// Other packages are optimized for specific use-cases.
// Codegen tools can also be used to produce concrete implementations of Node;
// these may be specific to certain data, but still conform to the Node
// interface for interoperability and to support higher-level functions.
//
// Nodes may also be *typed* -- see the 'schema' package and `schema.TypedNode`
// interface, which extends the Node interface with additional methods.
// Typed nodes have additional constraints and behaviors:
// for example, they may be a "struct" and have a specific type/structure
// to what data you can put inside them, but still behave as a regular Node
// in all ways this interface specifies (so you can traverse typed nodes, etc,
// without any additional special effort).
type Node interface {
// Kind returns a value from the Kind enum describing what the
// essential serializable kind of this node is (map, list, integer, etc).
// Most other handling of a node requires first switching upon the kind.
Kind() Kind
// LookupByString looks up a child object in this node and returns it.
// The returned Node may be any of the Kind:
// a primitive (string, int64, etc), a map, a list, or a link.
//
// If the Kind of this Node is not Kind_Map, a nil node and an error
// will be returned.
//
// If the key does not exist, a nil node and an error will be returned.
LookupByString(key string) (Node, error)
// LookupByNode is the equivalent of LookupByString, 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 `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 `schema.TypedNode`, and in this case prefer
// the LookupByNode(Node) method; otherwise, favor LookupByString; typically
// implementations will have their fastest paths thusly.)
LookupByNode(key Node) (Node, error)
// LookupByIndex is the equivalent of LookupByString but for indexing into a list.
// As with LookupByString, the returned Node may be any of the Kind:
// a primitive (string, int64, etc), a map, a list, or a link.
//
// If the Kind of this Node is not Kind_List, a nil node and an error
// will be returned.
//
// If idx is out of range, a nil node and an error will be returned.
LookupByIndex(idx int64) (Node, error)
// LookupBySegment is will act as either LookupByString or LookupByIndex,
// whichever is contextually appropriate.
//
// Using LookupBySegment may imply an "atoi" conversion if used on a list node,
// or an "itoa" conversion if used on a map node. If an "itoa" conversion
// takes place, it may error, and this method may return that error.
LookupBySegment(seg PathSegment) (Node, error)
// Note that when using codegenerated types, there may be a fifth variant
// of lookup method on maps: `Get($GeneratedTypeKey) $GeneratedTypeValue`!
// MapIterator returns an iterator which yields key-value pairs
// traversing the node.
// If the node kind is anything other than a map, nil will be returned.
//
// The iterator will yield every entry in the map; that is, it
// can be expected that itr.Next will be called node.Length times
// before itr.Done becomes true.
MapIterator() MapIterator
// ListIterator returns an iterator which traverses the node and yields indicies and list entries.
// If the node kind is anything other than a list, nil will be returned.
//
// The iterator will yield every entry in the list; that is, it
// can be expected that itr.Next will be called node.Length times
// before itr.Done becomes true.
//
// List iteration is ordered, and indices yielded during iteration will range from 0 to Node.Length-1.
// (The IPLD Data Model definition of lists only defines that it is an ordered list of elements;
// the definition does not include a concept of sparseness, so the indices are always sequential.)
ListIterator() ListIterator
// Length returns the length of a list, or the number of entries in a map,
// or -1 if the node is not of list nor map kind.
Length() int64
// Absent nodes are returned when traversing a struct field that is
// defined by a schema but unset in the data. (Absent nodes are not
// possible otherwise; you'll only see them from `schema.TypedNode`.)
// The absent flag is necessary so iterating over structs can
// unambiguously make the distinction between values that are
// present-and-null versus values that are absent.
//
// Absent nodes respond to `Kind()` as `ipld.Kind_Null`,
// for lack of any better descriptive value; you should therefore
// always check IsAbsent rather than just a switch on kind
// when it may be important to handle absent values distinctly.
IsAbsent() bool
IsNull() bool
AsBool() (bool, error)
AsInt() (int64, error)
AsFloat() (float64, error)
AsString() (string, error)
AsBytes() ([]byte, error)
AsLink() (Link, error)
// Prototype returns a NodePrototype which can describe some properties of this node's implementation,
// and also be used to get a NodeBuilder,
// which can be use to create new nodes with the same implementation as this one.
//
// For typed nodes, the NodePrototype will also implement schema.Type.
//
// For Advanced Data Layouts, the NodePrototype will encapsulate any additional
// parameters and configuration of the ADL, and will also (usually)
// implement NodePrototypeSupportingAmend.
//
// Calling this method should not cause an allocation.
Prototype() NodePrototype
}
// UintNode is an optional interface that can be used to represent an Int node
// that provides access to the full uint64 range.
//
// EXPERIMENTAL: this API is experimental and may be changed or removed in a
// future use. A future iteration may replace this with a BigInt interface to
// access a larger range of integers that may be enabled by alternative codecs.
type UintNode interface {
Node
// AsUint returns a uint64 representing the underlying integer if possible.
// This may return an error if the Node represents a negative integer that
// cannot be represented as a uint64.
AsUint() (uint64, error)
}
// LargeBytesNode is an optional interface extending a Bytes node that allows its
// contents to be accessed through an io.ReadSeeker instead of a []byte slice. Use of
// an io.Reader is encouraged, as it allows for streaming large byte slices
// without allocating a large slice in memory.
type LargeBytesNode interface {
Node
// AsLargeBytes returns an io.ReadSeeker that can be used to read the contents of the node.
// Note that the presence of this method / interface does not imply that the node
// can always return a valid io.ReadSeeker, and the error value must also be checked
// for support.
// It is not guaranteed that all implementations will implement the full semantics of
// Seek, in particular, they may refuse to seek to the end of a large bytes node if
// it is not possible to do so efficiently.
// The io.ReadSeeker returned by AsLargeBytes must be a seperate instance from subsequent
// calls to AsLargeBytes. Calls to read or seek on one returned instance should NOT
// affect the read position of other returned instances.
AsLargeBytes() (io.ReadSeeker, error)
}
// NodePrototype describes a node implementation (all Node have a NodePrototype),
// and a NodePrototype can always be used to get a NodeBuilder.
//
// A NodePrototype may also provide other information about implementation;
// such information is specific to this library ("prototype" isn't a concept
// you'll find in the IPLD Specifications), and is usually provided through
// feature-detection interfaces (for example, see NodePrototypeSupportingAmend).
//
// Generic algorithms for working with IPLD Nodes make use of NodePrototype
// to get builders for new nodes when creating data, and can also use the
// feature-detection interfaces to help decide what kind of operations
// will be optimal to use on a given node implementation.
//
// Note that NodePrototype is not the same as schema.Type.
// NodePrototype is a (golang-specific!) way to reflect upon the implementation
// and in-memory layout of some IPLD data.
// schema.Type is information about how a group of nodes is related in a schema
// (if they have one!) and the rules that the type mandates the node must follow.
// (Every node must have a prototype; but schema types are an optional feature.)
type NodePrototype interface {
// NewBuilder returns a NodeBuilder that can be used to create a new Node.
//
// Note that calling NewBuilder often performs an allocation
// (while in contrast, getting a NodePrototype typically does not!) --
// this may be consequential when writing high performance code.
NewBuilder() NodeBuilder
}
// NodePrototypeSupportingAmend is a feature-detection interface that can be
// used on a NodePrototype to see if it's possible to build new nodes of this style
// while sharing some internal data in a copy-on-write way.
//
// For example, Nodes using an Advanced Data Layout will typically
// support this behavior, and since ADLs are often used for handling large
// volumes of data, detecting and using this feature can result in significant
// performance savings.
type NodePrototypeSupportingAmend interface {
AmendingBuilder(base Node) NodeBuilder
// FUTURE: probably also needs a `AmendingWithout(base Node, filter func(k,v) bool) NodeBuilder`, or similar.
// ("deletion" based APIs are also possible but both more complicated in interfaces added, and prone to accidentally quadratic usage.)
// FUTURE: there should be some stdlib `Copy` (?) methods that automatically look for this feature, and fallback if absent.
// Might include a wide range of point `Transform`, etc, methods.
// FUTURE: consider putting this (and others like it) in a `feature` package, if there begin to be enough of them and docs get crowded.
}
// MapIterator is an interface for traversing map nodes.
// Sequential calls to Next() will yield key-value pairs;
// Done() describes whether iteration should continue.
//
// Iteration order is defined to be stable: two separate MapIterator
// created to iterate the same Node will yield the same key-value pairs
// in the same order.
// The order itself may be defined by the Node implementation: some
// Nodes may retain insertion order, and some may return iterators which
// always yield data in sorted order, for example.
type MapIterator interface {
// Next returns the next key-value pair.
//
// An error value can also be returned at any step: in the case of advanced
// data structures with incremental loading, it's possible to encounter
// cancellation or I/O errors at any point in iteration.
// If an error will be returned by the next call to Next,
// then the boolean returned by the Done method will be false
// (meaning it's acceptable to check Done first and move on if it's true,
// since that both means the iterator is complete and that there is no error).
// If an error is returned, the key and value may be nil.
Next() (key Node, value Node, err error)
// Done returns false as long as there's at least one more entry to iterate.
// When Done returns true, iteration can stop.
//
// Note when implementing iterators for advanced data layouts (e.g. more than
// one chunk of backing data, which is loaded incrementally): if your
// implementation does any I/O during the Done method, and it encounters
// an error, it must return 'false', so that the following Next call
// has an opportunity to return the error.
Done() bool
}
// ListIterator is an interface for traversing list nodes.
// Sequential calls to Next() will yield index-value pairs;
// Done() describes whether iteration should continue.
//
// ListIterator's Next method returns an index for convenience,
// but this number will always start at 0 and increment by 1 monotonically.
// A loop which iterates from 0 to Node.Length while calling Node.LookupByIndex
// is equivalent to using a ListIterator.
type ListIterator interface {
// Next returns the next index and value.
//
// An error value can also be returned at any step: in the case of advanced
// data structures with incremental loading, it's possible to encounter
// cancellation or I/O errors at any point in iteration.
// If an error will be returned by the next call to Next,
// then the boolean returned by the Done method will be false
// (meaning it's acceptable to check Done first and move on if it's true,
// since that both means the iterator is complete and that there is no error).
// If an error is returned, the key and value may be nil.
Next() (idx int64, value Node, err error)
// Done returns false as long as there's at least one more entry to iterate.
// When Done returns false, iteration can stop.
//
// Note when implementing iterators for advanced data layouts (e.g. more than
// one chunk of backing data, which is loaded incrementally): if your
// implementation does any I/O during the Done method, and it encounters
// an error, it must return 'false', so that the following Next call
// has an opportunity to return the error.
Done() bool
}
// REVIEW: immediate-mode AsBytes() method (as opposed to e.g. returning
// an io.Reader instance) might be problematic, esp. if we introduce
// AdvancedLayouts which support large bytes natively.
//
// Probable solution is having both immediate and iterator return methods.
// Returning a reader for bytes when you know you want a slice already
// is going to be high friction without purpose in many common uses.
//
// Unclear what SetByteStream() would look like for advanced layouts.
// One could try to encapsulate the chunking entirely within the advlay
// node impl... but would it be graceful? Not sure. Maybe. Hopefully!
// Yes? The advlay impl would still tend to use SetBytes for the raw
// data model layer nodes its composing, so overall, it shakes out nicely.

View File

@@ -0,0 +1,168 @@
package datamodel
// NodeAssembler is the interface that describes all the ways we can set values
// in a node that's under construction.
//
// A NodeAssembler is about filling in data.
// To create a new Node, you should start with a NodeBuilder (which contains a
// superset of the NodeAssembler methods, and can return the finished Node
// from its `Build` method).
// While continuing to build a recursive structure from there,
// you'll see NodeAssembler for all the child values.
//
// For filling scalar data, there's a `Assign{Kind}` method for each kind;
// after calling one of these methods, the data is filled in, and the assembler is done.
// For recursives, there are `BeginMap` and `BeginList` methods,
// which return an object that needs further manipulation to fill in the contents.
//
// There is also one special method: `AssignNode`.
// `AssignNode` takes another `Node` as a parameter,
// and should should internally call one of the other `Assign*` or `Begin*` (and subsequent) functions
// as appropriate for the kind of the `Node` it is given.
// This is roughly equivalent to using the `Copy` function (and is often implemented using it!), but
// `AssignNode` may also try to take faster shortcuts in some implementations, when it detects they're possible.
// (For example, for typed nodes, if they're the same type, lots of checking can be skipped.
// For nodes implemented with pointers, lots of copying can be skipped.
// For nodes that can detect the argument has the same memory layout, faster copy mechanisms can be used; etc.)
//
// Why do both this and the NodeBuilder interface exist?
// In short: NodeBuilder is when you want to cause an allocation;
// NodeAssembler can be used to just "fill in" memory.
// (In the internal gritty details: separate interfaces, one of which lacks a
// `Build` method, helps us write efficient library internals: avoiding the
// requirement to be able to return a Node at any random point in the process
// relieves internals from needing to implement 'freeze' features.
// This is useful in turn because implementing those 'freeze' features in a
// language without first-class/compile-time support for them (as golang is)
// would tend to push complexity and costs to execution time; we'd rather not.)
type NodeAssembler interface {
BeginMap(sizeHint int64) (MapAssembler, error)
BeginList(sizeHint int64) (ListAssembler, error)
AssignNull() error
AssignBool(bool) error
AssignInt(int64) error
AssignFloat(float64) error
AssignString(string) error
AssignBytes([]byte) error
AssignLink(Link) error
AssignNode(Node) error // if you already have a completely constructed subtree, this method puts the whole thing in place at once.
// Prototype returns a NodePrototype describing what kind of value we're assembling.
//
// You often don't need this (because you should be able to
// just feed data and check errors), but it's here.
//
// Using `this.Prototype().NewBuilder()` to produce a new `Node`,
// then giving that node to `this.AssignNode(n)` should always work.
// (Note that this is not necessarily an _exclusive_ statement on what
// sort of values will be accepted by `this.AssignNode(n)`.)
Prototype() NodePrototype
}
// MapAssembler assembles a map node! (You guessed it.)
//
// Methods on MapAssembler must be called in a valid order:
// assemble a key, then assemble a value, then loop as long as desired;
// when finished, call 'Finish'.
//
// Incorrect order invocations will panic.
// Calling AssembleKey twice in a row will panic;
// calling AssembleValue before finishing using the NodeAssembler from AssembleKey will panic;
// calling AssembleValue twice in a row will panic;
// etc.
//
// Note that the NodeAssembler yielded from AssembleKey has additional behavior:
// if the node assembled there matches a key already present in the map,
// that assembler will emit the error!
type MapAssembler interface {
AssembleKey() NodeAssembler // must be followed by call to AssembleValue.
AssembleValue() NodeAssembler // must be called immediately after AssembleKey.
AssembleEntry(k string) (NodeAssembler, error) // shortcut combining AssembleKey and AssembleValue into one step; valid when the key is a string kind.
Finish() error
// KeyPrototype returns a NodePrototype that knows how to build keys of a type this map uses.
//
// You often don't need this (because you should be able to
// just feed data and check errors), but it's here.
//
// For all Data Model maps, this will answer with a basic concept of "string".
// For Schema typed maps, this may answer with a more complex type
// (potentially even a struct type or union type -- anything that can have a string representation).
KeyPrototype() NodePrototype
// ValuePrototype returns a NodePrototype that knows how to build values this map can contain.
//
// You often don't need this (because you should be able to
// just feed data and check errors), but it's here.
//
// ValuePrototype requires a parameter describing the key in order to say what
// NodePrototype will be acceptable as a value for that key, because when using
// struct types (or union types) from the Schemas system, they behave as maps
// but have different acceptable types for each field (or member, for unions).
// For plain maps (that is, not structs or unions masquerading as maps),
// the empty string can be used as a parameter, and the returned NodePrototype
// can be assumed applicable for all values.
// Using an empty string for a struct or union will return nil,
// as will using any string which isn't a field or member of those types.
//
// (Design note: a string is sufficient for the parameter here rather than
// a full Node, because the only cases where the value types vary are also
// cases where the keys may not be complex.)
ValuePrototype(k string) NodePrototype
}
type ListAssembler interface {
AssembleValue() NodeAssembler
Finish() error
// ValuePrototype returns a NodePrototype that knows how to build values this map can contain.
//
// You often don't need this (because you should be able to
// just feed data and check errors), but it's here.
//
// ValuePrototype, much like the matching method on the MapAssembler interface,
// requires a parameter specifying the index in the list in order to say
// what NodePrototype will be acceptable as a value at that position.
// For many lists (and *all* lists which operate exclusively at the Data Model level),
// this will return the same NodePrototype regardless of the value of 'idx';
// the only time this value will vary is when operating with a Schema,
// and handling the representation NodeAssembler for a struct type with
// a representation of a list kind.
// If you know you are operating in a situation that won't have varying
// NodePrototypes, it is acceptable to call `ValuePrototype(0)` and use the
// resulting NodePrototype for all reasoning.
ValuePrototype(idx int64) NodePrototype
}
type NodeBuilder interface {
NodeAssembler
// Build returns the new value after all other assembly has been completed.
//
// A method on the NodeAssembler that finishes assembly of the data must
// be called first (e.g., any of the "Assign*" methods, or "Finish" if
// the assembly was for a map or a list); that finishing method still has
// all responsibility for validating the assembled data and returning
// any errors from that process.
// (Correspondingly, there is no error return from this method.)
//
// Note that building via a representation-level NodePrototype or NodeBuilder
// returns a node at the type level which implements schema.TypedNode.
// To obtain the representation-level node, you can do:
//
// // builder is at the representation level, so it returns typed nodes
// node := builder.Build().(schema.TypedNode)
// reprNode := node.Representation()
Build() Node
// Resets the builder. It can hereafter be used again.
// Reusing a NodeBuilder can reduce allocations and improve performance.
//
// Only call this if you're going to reuse the builder.
// (Otherwise, it's unnecessary, and may cause an unwanted allocation).
Reset()
}

231
vendor/github.com/ipld/go-ipld-prime/datamodel/path.go generated vendored Normal file
View File

@@ -0,0 +1,231 @@
package datamodel
import (
"strings"
)
// Path describes a series of steps across a tree or DAG of Node,
// where each segment in the path is a map key or list index
// (literaly, Path is a slice of PathSegment values).
// Path is used in describing progress in a traversal; and
// can also be used as an instruction for traversing from one Node to another.
// Path values will also often be encountered as part of error messages.
//
// (Note that Paths are useful as an instruction for traversing from
// *one* Node to *one* other Node; to do a walk from one Node and visit
// *several* Nodes based on some sort of pattern, look to IPLD Selectors,
// and the 'traversal/selector' package in this project.)
//
// Path values are always relative.
// Observe how 'traversal.Focus' requires both a Node and a Path argument --
// where to start, and where to go, respectively.
// Similarly, error values which include a Path will be speaking in reference
// to the "starting Node" in whatever context they arose from.
//
// The canonical form of a Path is as a list of PathSegment.
// Each PathSegment is a string; by convention, the string should be
// in UTF-8 encoding and use NFC normalization, but all operations
// will regard the string as its constituent eight-bit bytes.
//
// There are no illegal or magical characters in IPLD Paths
// (in particular, do not mistake them for UNIX system paths).
// IPLD Paths can only go down: that is, each segment must traverse one node.
// There is no ".." which means "go up";
// and there is no "." which means "stay here".
// IPLD Paths have no magic behavior around characters such as "~".
// IPLD Paths do not have a concept of "globs" nor behave specially
// for a path segment string of "*" (but you may wish to see 'Selectors'
// for globbing-like features that traverse over IPLD data).
//
// An empty string is a valid PathSegment.
// (This leads to some unfortunate complications when wishing to represent
// paths in a simple string format; however, consider that maps do exist
// in serialized data in the wild where an empty string is used as the key:
// it is important we be able to correctly describe and address this!)
//
// A string containing "/" (or even being simply "/"!) is a valid PathSegment.
// (As with empty strings, this is unfortunate (in particular, because it
// very much doesn't match up well with expectations popularized by UNIX-like
// filesystems); but, as with empty strings, maps which contain such a key
// certainly exist, and it is important that we be able to regard them!)
//
// A string starting, ending, or otherwise containing the NUL (\x00) byte
// is also a valid PathSegment. This follows from the rule of "a string is
// regarded as its constituent eight-bit bytes": an all-zero byte is not exceptional.
// In golang, this doesn't pose particular difficulty, but note this would be
// of marked concern for languages which have "C-style nul-terminated strings".
//
// For an IPLD Path to be represented as a string, an encoding system
// including escaping is necessary. At present, there is not a single
// canonical specification for such an escaping; we expect to decide one
// in the future, but this is not yet settled and done.
// (This implementation has a 'String' method, but it contains caveats
// and may be ambiguous for some content. This may be fixed in the future.)
type Path struct {
segments []PathSegment
}
// NewPath returns a Path composed of the given segments.
//
// This constructor function does a defensive copy,
// in case your segments slice should mutate in the future.
// (Use NewPathNocopy if this is a performance concern,
// and you're sure you know what you're doing.)
func NewPath(segments []PathSegment) Path {
p := Path{make([]PathSegment, len(segments))}
copy(p.segments, segments)
return p
}
// NewPathNocopy is identical to NewPath but trusts that
// the segments slice you provide will not be mutated.
func NewPathNocopy(segments []PathSegment) Path {
return Path{segments}
}
// ParsePath converts a string to an IPLD Path, doing a basic parsing of the
// string using "/" as a delimiter to produce a segmented Path.
// This is a handy, but not a general-purpose nor spec-compliant (!),
// way to create a Path: it cannot represent all valid paths.
//
// Multiple subsequent "/" characters will be silently collapsed.
// E.g., `"foo///bar"` will be treated equivalently to `"foo/bar"`.
// Prefixed and suffixed extraneous "/" characters are also discarded.
// This makes this constructor incapable of handling some possible Path values
// (specifically: paths with empty segements cannot be created with this constructor).
//
// There is no escaping mechanism used by this function.
// This makes this constructor incapable of handling some possible Path values
// (specifically, a path segment containing "/" cannot be created, because it
// will always be intepreted as a segment separator).
//
// No other "cleaning" of the path occurs. See the documentation of the Path struct;
// in particular, note that ".." does not mean "go up", nor does "." mean "stay here" --
// correspondingly, there isn't anything to "clean" in the same sense as
// 'filepath.Clean' from the standard library filesystem path packages would.
//
// If the provided string contains unprintable characters, or non-UTF-8
// or non-NFC-canonicalized bytes, no remark will be made about this,
// and those bytes will remain part of the PathSegments in the resulting Path.
func ParsePath(pth string) Path {
// FUTURE: we should probably have some escaping mechanism which makes
// it possible to encode a slash in a segment. Specification needed.
ss := strings.FieldsFunc(pth, func(r rune) bool { return r == '/' })
ssl := len(ss)
p := Path{make([]PathSegment, ssl)}
for i := 0; i < ssl; i++ {
p.segments[i] = PathSegmentOfString(ss[i])
}
return p
}
// String representation of a Path is simply the join of each segment with '/'.
// It does not include a leading nor trailing slash.
//
// This is a handy, but not a general-purpose nor spec-compliant (!),
// way to reduce a Path to a string.
// There is no escaping mechanism used by this function,
// and as a result, not all possible valid Path values (such as those with
// empty segments or with segments containing "/") can be encoded unambiguously.
// For Path values containing these problematic segments, ParsePath applied
// to the string returned from this function may return a nonequal Path value.
//
// No escaping for unprintable characters is provided.
// No guarantee that the resulting string is UTF-8 nor NFC canonicalized
// is provided unless all the constituent PathSegment had those properties.
func (p Path) String() string {
l := len(p.segments)
if l == 0 {
return ""
}
sb := strings.Builder{}
for i := 0; i < l-1; i++ {
sb.WriteString(p.segments[i].String())
sb.WriteByte('/')
}
sb.WriteString(p.segments[l-1].String())
return sb.String()
}
// Segments returns a slice of the path segment strings.
//
// It is not lawful to mutate nor append the returned slice.
func (p Path) Segments() []PathSegment {
return p.segments
}
// Len returns the number of segments in this path.
//
// Zero segments means the path refers to "the current node".
// One segment means it refers to a child of the current node; etc.
func (p Path) Len() int {
return len(p.segments)
}
// Join creates a new path composed of the concatenation of this and the given path's segments.
func (p Path) Join(p2 Path) Path {
combinedSegments := make([]PathSegment, len(p.segments)+len(p2.segments))
copy(combinedSegments, p.segments)
copy(combinedSegments[len(p.segments):], p2.segments)
p.segments = combinedSegments
return p
}
// AppendSegment is as per Join, but a shortcut when appending single segments.
func (p Path) AppendSegment(ps PathSegment) Path {
l := len(p.segments)
combinedSegments := make([]PathSegment, l+1)
copy(combinedSegments, p.segments)
combinedSegments[l] = ps
p.segments = combinedSegments
return p
}
// AppendSegmentString is as per AppendSegment, but a shortcut when the segment is a string.
func (p Path) AppendSegmentString(ps string) Path {
return p.AppendSegment(PathSegmentOfString(ps))
}
// AppendSegmentInt is as per AppendSegment, but a shortcut when the segment is an int.
func (p Path) AppendSegmentInt(ps int64) Path {
return p.AppendSegment(PathSegmentOfInt(ps))
}
// Parent returns a path with the last of its segments popped off (or
// the zero path if it's already empty).
func (p Path) Parent() Path {
if len(p.segments) == 0 {
return Path{}
}
return Path{p.segments[0 : len(p.segments)-1]}
}
// Truncate returns a path with only as many segments remaining as requested.
func (p Path) Truncate(i int) Path {
return Path{p.segments[0:i]}
}
// Last returns the trailing segment of the path.
func (p Path) Last() PathSegment {
if len(p.segments) < 1 {
return PathSegment{}
}
return p.segments[len(p.segments)-1]
}
// Pop returns a path with all segments except the last.
func (p Path) Pop() Path {
if len(p.segments) < 1 {
return Path{}
}
return Path{p.segments[0 : len(p.segments)-1]}
}
// Shift returns the first segment of the path together with the remaining path after that first segment.
// If applied to a zero-length path, it returns an empty segment and the same zero-length path.
func (p Path) Shift() (PathSegment, Path) {
if len(p.segments) < 1 {
return PathSegment{}, Path{}
}
return p.segments[0], Path{p.segments[1:]}
}

View File

@@ -0,0 +1,135 @@
package datamodel
import (
"strconv"
)
// PathSegment can describe either a key in a map, or an index in a list.
//
// Create a PathSegment via either ParsePathSegment, PathSegmentOfString,
// or PathSegmentOfInt; or, via one of the constructors of Path,
// which will implicitly create PathSegment internally.
// Using PathSegment's natural zero value directly is discouraged
// (it will act like ParsePathSegment("0"), which likely not what you'd expect).
//
// Path segments are "stringly typed" -- they may be interpreted as either strings or ints depending on context.
// A path segment of "123" will be used as a string when traversing a node of map kind;
// and it will be converted to an integer when traversing a node of list kind.
// (If a path segment string cannot be parsed to an int when traversing a node of list kind, then traversal will error.)
// It is not possible to ask which kind (string or integer) a PathSegment is, because that is not defined -- this is *only* intepreted contextually.
//
// Internally, PathSegment will store either a string or an integer,
// depending on how it was constructed,
// and will automatically convert to the other on request.
// (This means if two pieces of code communicate using PathSegment,
// one producing ints and the other expecting ints,
// then they will work together efficiently.)
// PathSegment in a Path produced by ParsePath generally have all strings internally,
// because there is no distinction possible when parsing a Path string
// (and attempting to pre-parse all strings into ints "just in case" would waste time in almost all cases).
//
// Be cautious of attempting to use PathSegment as a map key!
// Due to the implementation detail of internal storage, it's possible for
// PathSegment values which are "equal" per PathSegment.Equal's definition
// to still be unequal in the eyes of golang's native maps.
// You should probably use the string values of the PathSegment as map keys.
// (This has the additional bonus of hitting a special fastpath that the golang
// built-in maps have specifically for plain string keys.)
type PathSegment struct {
/*
A quick implementation note about the Go compiler and "union" semantics:
There are roughly two ways to do "union" semantics in Go.
The first is to make a struct with each of the values.
The second is to make an interface and use an unexported method to keep it closed.
The second tactic provides somewhat nicer semantics to the programmer.
(Namely, it's clearly impossible to have two inhabitants, which is... the point.)
The downside is... putting things in interfaces generally incurs an allocation
(grep your assembly output for "runtime.conv*").
The first tactic looks kludgier, and would seem to waste memory
(the struct reserves space for each possible value, even though the semantic is that only one may be non-zero).
However, in most cases, more *bytes* are cheaper than more *allocs* --
garbage collection costs are domininated by alloc count, not alloc size.
Because PathSegment is something we expect to put in fairly "hot" paths,
we're using the first tactic.
(We also currently get away with having no extra discriminator bit
because we use a signed int for indexes, and negative values aren't valid there,
and thus we can use it as a sentinel value.
(Fun note: Empty strings were originally used for this sentinel,
but it turns out empty strings are valid PathSegment themselves, so!))
*/
s string
i int64
}
// ParsePathSegment parses a string into a PathSegment,
// handling any escaping if present.
// (Note: there is currently no escaping specified for PathSegments,
// so this is currently functionally equivalent to PathSegmentOfString.)
func ParsePathSegment(s string) PathSegment {
return PathSegment{s: s, i: -1}
}
// PathSegmentOfString boxes a string into a PathSegment.
// It does not attempt to parse any escaping; use ParsePathSegment for that.
func PathSegmentOfString(s string) PathSegment {
return PathSegment{s: s, i: -1}
}
// PathSegmentOfString boxes an int into a PathSegment.
func PathSegmentOfInt(i int64) PathSegment {
return PathSegment{i: i}
}
// containsString is unexported because we use it to see what our *storage* form is,
// but this is considered an implementation detail that's non-semantic.
// If it returns false, it implicitly means "containsInt", as these are the only options.
func (ps PathSegment) containsString() bool {
return ps.i < 0
}
// String returns the PathSegment as a string.
func (ps PathSegment) String() string {
switch ps.containsString() {
case true:
return ps.s
case false:
return strconv.FormatInt(ps.i, 10)
}
panic("unreachable")
}
// Index returns the PathSegment as an integer,
// or returns an error if the segment is a string that can't be parsed as an int.
func (ps PathSegment) Index() (int64, error) {
switch ps.containsString() {
case true:
return strconv.ParseInt(ps.s, 10, 64)
case false:
return ps.i, nil
}
panic("unreachable")
}
// Equals checks if two PathSegment values are equal.
//
// Because PathSegment is "stringly typed", this comparison does not
// regard if one of the segments is stored as a string and one is stored as an int;
// if string values of two segments are equal, they are "equal" overall.
// In other words, `PathSegmentOfInt(2).Equals(PathSegmentOfString("2")) == true`!
// (You should still typically prefer this method over converting two segments
// to string and comparing those, because even though that may be functionally
// correct, this method will be faster if they're both ints internally.)
func (x PathSegment) Equals(o PathSegment) bool {
if !x.containsString() && !o.containsString() {
return x.i == o.i
}
return x.String() == o.String()
}

148
vendor/github.com/ipld/go-ipld-prime/datamodel/unit.go generated vendored Normal file
View File

@@ -0,0 +1,148 @@
package datamodel
// Null is the one kind of node we can have a true singleton implementation of.
// This is that value.
// The Null Node has Kind() == Kind_Null, returns IsNull() == true,
// returns ErrWrongKind to most other inquiries (as you'd expect),
// and returns a NodePrototype with a NewBuilder method that simply panics
// (because why would you ever try to build a new "null"?).
var Null Node = nullNode{}
type nullNode struct{}
func (nullNode) Kind() Kind {
return Kind_Null
}
func (nullNode) LookupByString(key string) (Node, error) {
return nil, ErrWrongKind{TypeName: "null", MethodName: "LookupByString", AppropriateKind: KindSet_JustMap, ActualKind: Kind_Null}
}
func (nullNode) LookupByNode(key Node) (Node, error) {
return nil, ErrWrongKind{TypeName: "null", MethodName: "LookupByNode", AppropriateKind: KindSet_JustMap, ActualKind: Kind_Null}
}
func (nullNode) LookupByIndex(idx int64) (Node, error) {
return nil, ErrWrongKind{TypeName: "null", MethodName: "LookupByIndex", AppropriateKind: KindSet_JustList, ActualKind: Kind_Null}
}
func (nullNode) LookupBySegment(seg PathSegment) (Node, error) {
return nil, ErrWrongKind{TypeName: "null", MethodName: "LookupBySegment", AppropriateKind: KindSet_Recursive, ActualKind: Kind_Null}
}
func (nullNode) MapIterator() MapIterator {
return nil
}
func (nullNode) ListIterator() ListIterator {
return nil
}
func (nullNode) Length() int64 {
return -1
}
func (nullNode) IsAbsent() bool {
return false
}
func (nullNode) IsNull() bool {
return true
}
func (nullNode) AsBool() (bool, error) {
return false, ErrWrongKind{TypeName: "null", MethodName: "AsBool", AppropriateKind: KindSet_JustBool, ActualKind: Kind_Null}
}
func (nullNode) AsInt() (int64, error) {
return 0, ErrWrongKind{TypeName: "null", MethodName: "AsInt", AppropriateKind: KindSet_JustInt, ActualKind: Kind_Null}
}
func (nullNode) AsFloat() (float64, error) {
return 0, ErrWrongKind{TypeName: "null", MethodName: "AsFloat", AppropriateKind: KindSet_JustFloat, ActualKind: Kind_Null}
}
func (nullNode) AsString() (string, error) {
return "", ErrWrongKind{TypeName: "null", MethodName: "AsString", AppropriateKind: KindSet_JustString, ActualKind: Kind_Null}
}
func (nullNode) AsBytes() ([]byte, error) {
return nil, ErrWrongKind{TypeName: "null", MethodName: "AsBytes", AppropriateKind: KindSet_JustBytes, ActualKind: Kind_Null}
}
func (nullNode) AsLink() (Link, error) {
return nil, ErrWrongKind{TypeName: "null", MethodName: "AsLink", AppropriateKind: KindSet_JustLink, ActualKind: Kind_Null}
}
func (nullNode) Prototype() NodePrototype {
return nullPrototype{}
}
type nullPrototype struct{}
func (nullPrototype) NewBuilder() NodeBuilder {
panic("cannot build null nodes")
}
// Absent is the _other_ kind of node (besides Null) we can have a true singleton implementation of.
// This is the singleton value for Absent.
// The Absent Node has Kind() == Kind_Null, returns IsNull() == false (!),
// returns IsAbsent() == true,
// returns ErrWrongKind to most other inquiries (as you'd expect),
// and returns a NodePrototype with a NewBuilder method that simply panics
// (because why would you ever try to build a new "nothing"?).
//
// Despite its presence in the datamodel package, "absent" is not really a data model concept.
// Absent should not really be seen in any datamodel Node implementations, for example.
// Absent is seen used in the realm of schemas and typed data, because there,
// there's a concept of data that has been described, and yet is not currently present;
// it is this concept that "absent" is used to express.
// Absent also sometimes shows up as a distinct case in codecs or other diagnostic printing,
// and suchlike miscellaneous places; it is for these reasons that it's here in the datamodel package,
// because it would end up imported somewhat universally for those diagnostic purposes anyway.
// (This may be worth a design review at some point, but holds up well enough for now.)
var Absent Node = absentNode{}
type absentNode struct{}
func (absentNode) Kind() Kind {
return Kind_Null
}
func (absentNode) LookupByString(key string) (Node, error) {
return nil, ErrWrongKind{TypeName: "absent", MethodName: "LookupByString", AppropriateKind: KindSet_JustMap, ActualKind: Kind_Null}
}
func (absentNode) LookupByNode(key Node) (Node, error) {
return nil, ErrWrongKind{TypeName: "absent", MethodName: "LookupByNode", AppropriateKind: KindSet_JustMap, ActualKind: Kind_Null}
}
func (absentNode) LookupByIndex(idx int64) (Node, error) {
return nil, ErrWrongKind{TypeName: "absent", MethodName: "LookupByIndex", AppropriateKind: KindSet_JustList, ActualKind: Kind_Null}
}
func (absentNode) LookupBySegment(seg PathSegment) (Node, error) {
return nil, ErrWrongKind{TypeName: "absent", MethodName: "LookupBySegment", AppropriateKind: KindSet_Recursive, ActualKind: Kind_Null}
}
func (absentNode) MapIterator() MapIterator {
return nil
}
func (absentNode) ListIterator() ListIterator {
return nil
}
func (absentNode) Length() int64 {
return -1
}
func (absentNode) IsAbsent() bool {
return true
}
func (absentNode) IsNull() bool {
return false
}
func (absentNode) AsBool() (bool, error) {
return false, ErrWrongKind{TypeName: "absent", MethodName: "AsBool", AppropriateKind: KindSet_JustBool, ActualKind: Kind_Null}
}
func (absentNode) AsInt() (int64, error) {
return 0, ErrWrongKind{TypeName: "absent", MethodName: "AsInt", AppropriateKind: KindSet_JustInt, ActualKind: Kind_Null}
}
func (absentNode) AsFloat() (float64, error) {
return 0, ErrWrongKind{TypeName: "absent", MethodName: "AsFloat", AppropriateKind: KindSet_JustFloat, ActualKind: Kind_Null}
}
func (absentNode) AsString() (string, error) {
return "", ErrWrongKind{TypeName: "absent", MethodName: "AsString", AppropriateKind: KindSet_JustString, ActualKind: Kind_Null}
}
func (absentNode) AsBytes() ([]byte, error) {
return nil, ErrWrongKind{TypeName: "absent", MethodName: "AsBytes", AppropriateKind: KindSet_JustBytes, ActualKind: Kind_Null}
}
func (absentNode) AsLink() (Link, error) {
return nil, ErrWrongKind{TypeName: "absent", MethodName: "AsLink", AppropriateKind: KindSet_JustLink, ActualKind: Kind_Null}
}
func (absentNode) Prototype() NodePrototype {
return absentPrototype{}
}
type absentPrototype struct{}
func (absentPrototype) NewBuilder() NodeBuilder {
panic("cannot build absent nodes")
}

68
vendor/github.com/ipld/go-ipld-prime/doc.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
// go-ipld-prime is a series of go interfaces for manipulating IPLD data.
//
// See https://ipld.io/ for more information about the basics
// of "What is IPLD?".
//
// Here in the godoc, the first couple of types to look at should be:
//
// - Node
// - NodeBuilder and NodeAssembler
// - NodePrototype.
//
// These types provide a generic description of the data model.
//
// A Node is a piece of IPLD data which can be inspected.
// A NodeAssembler is used to create Nodes.
// (A NodeBuilder is just like a NodeAssembler, but allocates memory
// (whereas a NodeAssembler just fills up memory; using these carefully
// allows construction of very efficient code.)
//
// Different NodePrototypes can be used to describe Nodes which follow certain logical rules
// (e.g., we use these as part of implementing Schemas),
// and can also be used so that programs can use different memory layouts for different data
// (which can be useful for constructing efficient programs when data has known shape for
// which we can use specific or compacted memory layouts).
//
// If working with linked data (data which is split into multiple
// trees of Nodes, loaded separately, and connected by some kind of
// "link" reference), the next types you should look at are:
//
// - LinkSystem
// - ... and its fields.
//
// The most typical use of LinkSystem is to use the linking/cid package
// to get a LinkSystem that works with CIDs:
//
// lsys := cidlink.DefaultLinkSystem()
//
// ... and then assign the StorageWriteOpener and StorageReadOpener fields
// in order to control where data is stored to and read from.
// Methods on the LinkSystem then provide the functions typically used
// to get data in and out of Nodes so you can work with it.
//
// This root package gathers some of the most important ease-of-use functions
// all in one place, but is mostly aliases out to features originally found
// in other more specific sub-packages. (If you're interested in keeping
// your binary sizes small, and don't use some of the features of this library,
// you'll probably want to look into using the relevant sub-packages directly.)
//
// Particularly interesting subpackages include:
//
// - datamodel -- the most essential interfaces for describing data live here,
// describing Node, NodePrototype, NodeBuilder, Link, and Path.
// - node/* -- various Node + NodeBuilder implementations.
// - node/basicnode -- the first Node implementation you should try.
// - codec/* -- functions for serializing and deserializing Nodes.
// - linking -- the LinkSystem, which is a facade to all data loading and storing and hashing.
// - linking/* -- ways to bind concrete Link implementations (namely,
// the linking/cidlink package, which connects the go-cid library to our datamodel.Link interface).
// - traversal -- functions for walking Node graphs (including automatic link loading)
// and visiting them programmatically.
// - traversal/selector -- functions for working with IPLD Selectors,
// which are a language-agnostic declarative format for describing graph walks.
// - fluent/* -- various options for making datamodel Node and NodeBuilder easier to work with.
// - schema -- interfaces for working with IPLD Schemas, which can bring constraints
// and validation systems to otherwise schemaless and unstructured IPLD data.
// - adl/* -- examples of creating and using Advanced Data Layouts (in short, custom Node implementations)
// to do complex data structures transparently within the IPLD Data Model.
package ipld

17
vendor/github.com/ipld/go-ipld-prime/linking.go generated vendored Normal file
View File

@@ -0,0 +1,17 @@
package ipld
import (
"github.com/ipld/go-ipld-prime/linking"
)
type (
LinkSystem = linking.LinkSystem
LinkContext = linking.LinkContext
)
type (
BlockReadOpener = linking.BlockReadOpener
BlockWriteOpener = linking.BlockWriteOpener
BlockWriteCommitter = linking.BlockWriteCommitter
NodeReifier = linking.NodeReifier
)

View File

@@ -0,0 +1,57 @@
Why does this package exist?
----------------------------
The `linking/cid` package bends the `github.com/ipfs/go-cid` package into conforming to the `ipld.Link` interface.
The `linking/cid` package also contains factory functions for `ipld.LinkSystem`.
These LinkSystem will be constructed with `EncoderChooser`, `DecoderChooser`, and `HasherChooser` funcs
which will use multicodec registries and multihash registries respectively.
### Why not use go-cid directly?
We need a "Link" interface in the root `ipld` package or things just aren't definable.
But we don't want the root `ipld.Link` concept to directly map to `go-cid.Cid` for several reasons:
1. We might want to revisit the go-cid library. Possibly in the "significantly breaking changes" sense.
- It's also not clear when we might do this -- and if we do, the transition period will be *long* because it's a highly-depended-upon library.
- See below for some links to a gist that discusses why.
2. We might want to extend the concept of linking to more than just plain CIDs.
- This is hypothetical at present -- but an often-discussed example is "what if CID+Path was also a Link?"
3. We might sometimes want to use IPLD libraries without using any CID implementation at all.
- e.g. it's totally believable to want to use IPLD libraries for handling JSON and CBOR, even if you don't want IPLD linking.
- if the CID packages were cheap enough, maybe this concern would fade -- but right now, they're **definitely** not; the transitive dependency tree of go-cid is *huge*.
#### If go-cid is revisited, what might that look like?
No idea. (At least, not in a committal way.)
https://gist.github.com/warpfork/e871b7fee83cb814fb1f043089983bb3#existing-implementations
gathers some reflections on the problems that would be nice to solve, though.
https://gist.github.com/warpfork/e871b7fee83cb814fb1f043089983bb3#file-cid-go
contains a draft outline of what a revisited API could look like,
but note that at the time of writing, it is not strongly ratified nor in any way committed to.
At any rate, though, the operative question for this package is:
if we do revisit go-cid, how are we going to make the transition managable?
It seems unlikely we'd be able to make the transition manageable without some interface, somewhere.
So we might as well draw that line at `ipld.Link`.
(I hypothesize that a transition story might involve two CID packages,
which could grow towards a shared interface,
doing so in a way that's purely additive in the established `go-cid` package.
We'd need two separate go modules to do this, since the aim is reducing dependency bloat for those that use the new one.
The shared interface in this story could have more info than `ipld.Link` does now,
but would nonetheless still certainly be an interface in order to support the separation of modules.)
### Why are LinkSystem factory functions here, instead of in the main IPLD package?
Same reason as why we don't use go-cid directly.
If we put these LinkSystem defaults in the root `ipld` package,
we'd bring on all the transitive dependencies of `go-cid` onto an user of `ipld` unconditionally...
and we don't want to do that.
You know that Weird Al song "It's all about the pentiums"?
Retune that in your mind to "It's all about dependencies".

View File

@@ -0,0 +1,76 @@
package cidlink
import (
"fmt"
cid "github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime/datamodel"
multihash "github.com/multiformats/go-multihash"
)
var (
_ datamodel.Link = Link{}
_ datamodel.LinkPrototype = LinkPrototype{}
)
// Link implements the datamodel.Link interface using a CID.
// See https://github.com/ipfs/go-cid for more information about CIDs.
//
// When using this value, typically you'll use it as `Link`, and not `*Link`.
// This includes when handling the value as an `datamodel.Link` interface -- the non-pointer form is typically preferable.
// This is because the datamodel.Link inteface is often desirable to be able to use as a golang map key,
// and in that context, pointers would not result in the desired behavior.
type Link struct {
cid.Cid
}
func (lnk Link) Prototype() datamodel.LinkPrototype {
return LinkPrototype{lnk.Cid.Prefix()}
}
func (lnk Link) String() string {
return lnk.Cid.String()
}
func (lnk Link) Binary() string {
return lnk.Cid.KeyString()
}
type LinkPrototype struct {
cid.Prefix
}
func (lp LinkPrototype) BuildLink(hashsum []byte) datamodel.Link {
// Does this method body look surprisingly complex? I agree.
// We actually have to do all this work. The go-cid package doesn't expose a constructor that just lets us directly set the bytes and the prefix numbers next to each other.
// No, `cid.Prefix.Sum` is not the method you are looking for: that expects the whole data body.
// Most of the logic here is the same as the body of `cid.Prefix.Sum`; we just couldn't get at the relevant parts without copypasta.
// There is also some logic that's sort of folded in from the go-multihash module. This is really a mess.
// The go-cid package needs review. So does go-multihash. Their responsibilies are not well compartmentalized and they don't play well with other stdlib golang interfaces.
p := lp.Prefix
length := p.MhLength
if p.MhType == multihash.IDENTITY {
length = -1
}
if p.Version == 0 && (p.MhType != multihash.SHA2_256 ||
(p.MhLength != 32 && p.MhLength != -1)) {
panic(fmt.Errorf("invalid cid v0 prefix"))
}
if length != -1 {
hashsum = hashsum[:p.MhLength]
}
mh, err := multihash.Encode(hashsum, p.MhType)
if err != nil {
panic(err) // No longer possible, but multihash still returns an error for legacy reasons.
}
switch lp.Prefix.Version {
case 0:
return Link{cid.NewCidV0(mh)}
case 1:
return Link{cid.NewCidV1(p.Codec, mh)}
default:
panic(fmt.Errorf("invalid cid version"))
}
}

View File

@@ -0,0 +1,71 @@
package cidlink
import (
"fmt"
"hash"
"github.com/multiformats/go-multihash/core"
"github.com/ipld/go-ipld-prime/codec"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/linking"
"github.com/ipld/go-ipld-prime/multicodec"
)
// DefaultLinkSystem returns a linking.LinkSystem which uses cidlink.Link for datamodel.Link.
// During selection of encoders, decoders, and hashers, it examines the multicodec indicator numbers and multihash indicator numbers from the CID,
// and uses the default global multicodec registry (see the go-ipld-prime/multicodec package) for resolving codec implementations,
// and the default global multihash registry (see the go-multihash/core package) for resolving multihash implementations.
//
// No storage functions are present in the returned LinkSystem.
// The caller can assign those themselves as desired.
func DefaultLinkSystem() linking.LinkSystem {
return LinkSystemUsingMulticodecRegistry(multicodec.DefaultRegistry)
}
// LinkSystemUsingMulticodecRegistry is similar to DefaultLinkSystem, but accepts a multicodec.Registry as a parameter.
//
// This can help create a LinkSystem which uses different multicodec implementations than the global registry.
// (Sometimes this can be desired if you want some parts of a program to support a more limited suite of codecs than other parts of the program,
// or needed to use a different multicodec registry than the global one for synchronization purposes, or etc.)
func LinkSystemUsingMulticodecRegistry(mcReg multicodec.Registry) linking.LinkSystem {
return linking.LinkSystem{
EncoderChooser: func(lp datamodel.LinkPrototype) (codec.Encoder, error) {
switch lp2 := lp.(type) {
case LinkPrototype:
fn, err := mcReg.LookupEncoder(lp2.GetCodec())
if err != nil {
return nil, err
}
return fn, nil
default:
return nil, fmt.Errorf("this encoderChooser can only handle cidlink.LinkPrototype; got %T", lp)
}
},
DecoderChooser: func(lnk datamodel.Link) (codec.Decoder, error) {
lp := lnk.Prototype()
switch lp2 := lp.(type) {
case LinkPrototype:
fn, err := mcReg.LookupDecoder(lp2.GetCodec())
if err != nil {
return nil, err
}
return fn, nil
default:
return nil, fmt.Errorf("this decoderChooser can only handle cidlink.LinkPrototype; got %T", lp)
}
},
HasherChooser: func(lp datamodel.LinkPrototype) (hash.Hash, error) {
switch lp2 := lp.(type) {
case LinkPrototype:
h, err := multihash.GetHasher(lp2.MhType)
if err != nil {
return nil, fmt.Errorf("no hasher registered for multihash indicator 0x%x: %w", lp2.MhType, err)
}
return h, nil
default:
return nil, fmt.Errorf("this hasherChooser can only handle cidlink.LinkPrototype; got %T", lp)
}
},
}
}

View File

@@ -0,0 +1,56 @@
package cidlink
import (
"bytes"
"fmt"
"io"
"os"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/linking"
)
// Memory is a simple in-memory storage for cidlinks. It's the same as `storage.Memory`
// but uses typical multihash semantics used when reading/writing cidlinks.
//
// Using multihash as the storage key rather than the whole CID will remove the
// distinction between CIDv0 and their CIDv1 counterpart. It also removes the
// distinction between CIDs where the multihash is the same but the codec is
// different, e.g. `dag-cbor` and a `raw` version of the same data.
type Memory struct {
Bag map[string][]byte
}
func (store *Memory) beInitialized() {
if store.Bag != nil {
return
}
store.Bag = make(map[string][]byte)
}
func (store *Memory) OpenRead(lnkCtx linking.LinkContext, lnk datamodel.Link) (io.Reader, error) {
store.beInitialized()
cl, ok := lnk.(Link)
if !ok {
return nil, fmt.Errorf("incompatible link type: %T", lnk)
}
data, exists := store.Bag[string(cl.Hash())]
if !exists {
return nil, os.ErrNotExist
}
return bytes.NewReader(data), nil
}
func (store *Memory) OpenWrite(lnkCtx linking.LinkContext) (io.Writer, linking.BlockWriteCommitter, error) {
store.beInitialized()
buf := bytes.Buffer{}
return &buf, func(lnk datamodel.Link) error {
cl, ok := lnk.(Link)
if !ok {
return fmt.Errorf("incompatible link type: %T", lnk)
}
store.Bag[string(cl.Hash())] = buf.Bytes()
return nil
}, nil
}

30
vendor/github.com/ipld/go-ipld-prime/linking/errors.go generated vendored Normal file
View File

@@ -0,0 +1,30 @@
package linking
import (
"fmt"
"github.com/ipld/go-ipld-prime/datamodel"
)
// ErrLinkingSetup is returned by methods on LinkSystem when some part of the system is not set up correctly,
// or when one of the components refuses to handle a Link or LinkPrototype given.
// (It is not yielded for errors from the storage nor codec systems once they've started; those errors rise without interference.)
type ErrLinkingSetup struct {
Detail string // Perhaps an enum here as well, which states which internal function was to blame?
Cause error
}
func (e ErrLinkingSetup) Error() string { return fmt.Sprintf("%s: %v", e.Detail, e.Cause) }
func (e ErrLinkingSetup) Unwrap() error { return e.Cause }
// ErrHashMismatch is the error returned when loading data and verifying its hash
// and finding that the loaded data doesn't re-hash to the expected value.
// It is typically seen returned by functions like LinkSystem.Load or LinkSystem.Fill.
type ErrHashMismatch struct {
Actual datamodel.Link
Expected datamodel.Link
}
func (e ErrHashMismatch) Error() string {
return fmt.Sprintf("hash mismatch! %v (actual) != %v (expected)", e.Actual, e.Expected)
}

View File

@@ -0,0 +1,290 @@
package linking
import (
"bytes"
"context"
"io"
"github.com/ipld/go-ipld-prime/datamodel"
)
// This file contains all the functions on LinkSystem.
// These are the helpful, user-facing functions we expect folks to use "most of the time" when loading and storing data.
// Variations:
// - Load vs Store vs ComputeLink
// - Load vs LoadPlusRaw
// - With or without LinkContext?
// - Brevity would be nice but I can't think of what to name the functions, so: everything takes LinkContext. Zero value is fine though.
// - [for load direction only]: Prototype (and return Node|error) or Assembler (and just return error)?
// - naming: Load vs Fill.
// - 'Must' variants.
// Can we get as far as a `QuickLoad(lnk Link) (Node, error)` function, which doesn't even ask you for a NodePrototype?
// No, not quite. (Alas.) If we tried to do so, and make it use `basicnode.Prototype`, we'd have import cycles; ded.
// Load looks up some data identified by a Link, and does everything necessary to turn it into usable data.
// In detail, that means it:
// brings that data into memory,
// verifies the hash,
// parses it into the Data Model using a codec,
// and returns an IPLD Node.
//
// Where the data will be loaded from is determined by the configuration of the LinkSystem
// (namely, the StorageReadOpener callback, which can either be set directly,
// or configured via the SetReadStorage function).
//
// The in-memory form used for the returned Node is determined by the given NodePrototype parameter.
// A new builder and a new node will be allocated, via NodePrototype.NewBuilder.
// (If you'd like more control over memory allocation, you may wish to see the Fill function instead.)
//
// A schema may also be used, and apply additional data validation during loading,
// by using a schema.TypedNodePrototype as the NodePrototype argument.
//
// The LinkContext parameter may be used to pass contextual information down to the loading layer.
//
// Which hashing function is used to validate the loaded data is determined by LinkSystem.HasherChooser.
// Which codec is used to parse the loaded data into the Data Model is determined by LinkSystem.DecoderChooser.
//
// The LinkSystem.NodeReifier callback is also applied before returning the Node,
// and so Load may also thereby return an ADL.
func (lsys *LinkSystem) Load(lnkCtx LinkContext, lnk datamodel.Link, np datamodel.NodePrototype) (datamodel.Node, error) {
nb := np.NewBuilder()
if err := lsys.Fill(lnkCtx, lnk, nb); err != nil {
return nil, err
}
nd := nb.Build()
if lsys.NodeReifier == nil {
return nd, nil
}
return lsys.NodeReifier(lnkCtx, nd, lsys)
}
// MustLoad is identical to Load, but panics in the case of errors.
//
// This function is meant for convenience of use in test and demo code, but should otherwise probably be avoided.
func (lsys *LinkSystem) MustLoad(lnkCtx LinkContext, lnk datamodel.Link, np datamodel.NodePrototype) datamodel.Node {
if n, err := lsys.Load(lnkCtx, lnk, np); err != nil {
panic(err)
} else {
return n
}
}
// LoadPlusRaw is similar to Load, but additionally retains and returns the byte slice of the raw data parsed.
//
// Be wary of using this with large data, since it will hold all data in memory at once.
// For more control over streaming, you may want to construct a LinkSystem where you wrap the storage opener callbacks,
// and thus can access the streams (and tee them, or whatever you need to do) as they're opened.
// This function is meant for convenience when data sizes are small enough that fitting them into memory at once is not a problem.
func (lsys *LinkSystem) LoadPlusRaw(lnkCtx LinkContext, lnk datamodel.Link, np datamodel.NodePrototype) (datamodel.Node, []byte, error) {
// Choose all the parts.
decoder, err := lsys.DecoderChooser(lnk)
if err != nil {
return nil, nil, ErrLinkingSetup{"could not choose a decoder", err}
}
// Use LoadRaw to get the data.
// If we're going to have everything in memory at once, we might as well do that first, and then give the codec and the hasher the whole thing at once.
block, err := lsys.LoadRaw(lnkCtx, lnk)
if err != nil {
return nil, block, err
}
// Create a NodeBuilder.
// Deploy the codec.
// Build the node.
nb := np.NewBuilder()
if err := decoder(nb, bytes.NewBuffer(block)); err != nil {
return nil, block, err
}
nd := nb.Build()
// Consider applying NodeReifier, if applicable.
if lsys.NodeReifier == nil {
return nd, block, nil
}
nd, err = lsys.NodeReifier(lnkCtx, nd, lsys)
return nd, block, err
}
// LoadRaw looks up some data identified by a Link, brings that data into memory,
// verifies the hash, and returns it directly as a byte slice.
//
// LoadRaw does not return a data model view of the data,
// nor does it verify that a codec can parse the data at all!
// Use this function at your own risk; it does not provide the same guarantees as the Load or Fill functions do.
func (lsys *LinkSystem) LoadRaw(lnkCtx LinkContext, lnk datamodel.Link) ([]byte, error) {
if lnkCtx.Ctx == nil {
lnkCtx.Ctx = context.Background()
}
// Choose all the parts.
hasher, err := lsys.HasherChooser(lnk.Prototype())
if err != nil {
return nil, ErrLinkingSetup{"could not choose a hasher", err}
}
if lsys.StorageReadOpener == nil {
return nil, ErrLinkingSetup{"no storage configured for reading", io.ErrClosedPipe} // REVIEW: better cause?
}
// Open storage: get the data.
// FUTURE: this could probably use storage.ReadableStorage.Get instead of streaming and a buffer, if we refactored LinkSystem to carry that interface through.
reader, err := lsys.StorageReadOpener(lnkCtx, lnk)
if err != nil {
return nil, err
}
if closer, ok := reader.(io.Closer); ok {
defer closer.Close()
}
var buf bytes.Buffer
if _, err := io.Copy(&buf, reader); err != nil {
return nil, err
}
// Compute the hash.
// (Then do a bit of a jig to build a link out of it -- because that's what we do the actual hash equality check on.)
hasher.Write(buf.Bytes())
hash := hasher.Sum(nil)
lnk2 := lnk.Prototype().BuildLink(hash)
if lnk2.Binary() != lnk.Binary() {
return nil, ErrHashMismatch{Actual: lnk2, Expected: lnk}
}
// No codec to deploy; this is the raw load function.
// So we're done.
return buf.Bytes(), nil
}
// Fill is similar to Load, but allows more control over memory allocations.
// Instead of taking a NodePrototype parameter, Fill takes a NodeAssembler parameter:
// this allows you to use your own NodeBuilder (and reset it, etc, thus controlling allocations),
// or, to fill in some part of a larger structure.
//
// Note that Fill does not regard NodeReifier, even if one has been configured.
// (This is in contrast to Load, which does regard a NodeReifier if one is configured, and thus may return an ADL node).
func (lsys *LinkSystem) Fill(lnkCtx LinkContext, lnk datamodel.Link, na datamodel.NodeAssembler) error {
if lnkCtx.Ctx == nil {
lnkCtx.Ctx = context.Background()
}
// Choose all the parts.
decoder, err := lsys.DecoderChooser(lnk)
if err != nil {
return ErrLinkingSetup{"could not choose a decoder", err}
}
hasher, err := lsys.HasherChooser(lnk.Prototype())
if err != nil {
return ErrLinkingSetup{"could not choose a hasher", err}
}
if lsys.StorageReadOpener == nil {
return ErrLinkingSetup{"no storage configured for reading", io.ErrClosedPipe} // REVIEW: better cause?
}
// Open storage; get a reader stream.
reader, err := lsys.StorageReadOpener(lnkCtx, lnk)
if err != nil {
return err
}
if closer, ok := reader.(io.Closer); ok {
defer closer.Close()
}
// TrustedStorage indicates the data coming out of this reader has already been hashed and verified earlier.
// As a result, we can skip rehashing it
if lsys.TrustedStorage {
return decoder(na, reader)
}
// Tee the stream so that the hasher is fed as the unmarshal progresses through the stream.
tee := io.TeeReader(reader, hasher)
// The actual read is then dragged forward by the codec.
decodeErr := decoder(na, tee)
if decodeErr != nil {
// It is important to security to check the hash before returning any other observation about the content,
// so, if the decode process returns any error, we have several steps to take before potentially returning it.
// First, we try to copy any data remaining that wasn't already pulled through the TeeReader by the decoder,
// so that the hasher can reach the end of the stream.
// If _that_ errors, return the I/O level error.
// We hang onto decodeErr for a while: we can't return that until all the way after we check the hash equality.
_, err := io.Copy(hasher, reader)
if err != nil {
return err
}
}
// Compute the hash.
// (Then do a bit of a jig to build a link out of it -- because that's what we do the actual hash equality check on.)
hash := hasher.Sum(nil)
lnk2 := lnk.Prototype().BuildLink(hash)
if lnk2.Binary() != lnk.Binary() {
return ErrHashMismatch{Actual: lnk2, Expected: lnk}
}
// If we got all the way through IO and through the hash check:
// now, finally, if we did get an error from the codec, we can admit to that.
if decodeErr != nil {
return decodeErr
}
return nil
}
// MustFill is identical to Fill, but panics in the case of errors.
//
// This function is meant for convenience of use in test and demo code, but should otherwise probably be avoided.
func (lsys *LinkSystem) MustFill(lnkCtx LinkContext, lnk datamodel.Link, na datamodel.NodeAssembler) {
if err := lsys.Fill(lnkCtx, lnk, na); err != nil {
panic(err)
}
}
func (lsys *LinkSystem) Store(lnkCtx LinkContext, lp datamodel.LinkPrototype, n datamodel.Node) (datamodel.Link, error) {
if lnkCtx.Ctx == nil {
lnkCtx.Ctx = context.Background()
}
// Choose all the parts.
encoder, err := lsys.EncoderChooser(lp)
if err != nil {
return nil, ErrLinkingSetup{"could not choose an encoder", err}
}
hasher, err := lsys.HasherChooser(lp)
if err != nil {
return nil, ErrLinkingSetup{"could not choose a hasher", err}
}
if lsys.StorageWriteOpener == nil {
return nil, ErrLinkingSetup{"no storage configured for writing", io.ErrClosedPipe} // REVIEW: better cause?
}
// Open storage write stream, feed serial data to the storage and the hasher, and funnel the codec output into both.
writer, commitFn, err := lsys.StorageWriteOpener(lnkCtx)
if err != nil {
return nil, err
}
tee := io.MultiWriter(writer, hasher)
err = encoder(n, tee)
if err != nil {
return nil, err
}
lnk := lp.BuildLink(hasher.Sum(nil))
return lnk, commitFn(lnk)
}
func (lsys *LinkSystem) MustStore(lnkCtx LinkContext, lp datamodel.LinkPrototype, n datamodel.Node) datamodel.Link {
if lnk, err := lsys.Store(lnkCtx, lp, n); err != nil {
panic(err)
} else {
return lnk
}
}
// ComputeLink returns a Link for the given data, but doesn't do anything else
// (e.g. it doesn't try to store any of the serial-form data anywhere else).
func (lsys *LinkSystem) ComputeLink(lp datamodel.LinkPrototype, n datamodel.Node) (datamodel.Link, error) {
encoder, err := lsys.EncoderChooser(lp)
if err != nil {
return nil, ErrLinkingSetup{"could not choose an encoder", err}
}
hasher, err := lsys.HasherChooser(lp)
if err != nil {
return nil, ErrLinkingSetup{"could not choose a hasher", err}
}
err = encoder(n, hasher)
if err != nil {
return nil, err
}
return lp.BuildLink(hasher.Sum(nil)), nil
}
func (lsys *LinkSystem) MustComputeLink(lp datamodel.LinkPrototype, n datamodel.Node) datamodel.Link {
if lnk, err := lsys.ComputeLink(lp, n); err != nil {
panic(err)
} else {
return lnk
}
}

41
vendor/github.com/ipld/go-ipld-prime/linking/setup.go generated vendored Normal file
View File

@@ -0,0 +1,41 @@
package linking
import (
"io"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/storage"
)
// SetReadStorage configures how the LinkSystem will look for information to load,
// setting it to look at the given storage.ReadableStorage.
//
// This will overwrite the LinkSystem.StorageReadOpener field.
//
// This mechanism only supports setting exactly one ReadableStorage.
// If you would like to make a more complex configuration
// (for example, perhaps using information from a LinkContext to decide which storage area to use?)
// then you should set LinkSystem.StorageReadOpener to a custom callback of your own creation instead.
func (lsys *LinkSystem) SetReadStorage(store storage.ReadableStorage) {
lsys.StorageReadOpener = func(lctx LinkContext, lnk datamodel.Link) (io.Reader, error) {
return storage.GetStream(lctx.Ctx, store, lnk.Binary())
}
}
// SetWriteStorage configures how the LinkSystem will store information,
// setting it to write into the given storage.WritableStorage.
//
// This will overwrite the LinkSystem.StorageWriteOpener field.
//
// This mechanism only supports setting exactly one WritableStorage.
// If you would like to make a more complex configuration
// (for example, perhaps using information from a LinkContext to decide which storage area to use?)
// then you should set LinkSystem.StorageWriteOpener to a custom callback of your own creation instead.
func (lsys *LinkSystem) SetWriteStorage(store storage.WritableStorage) {
lsys.StorageWriteOpener = func(lctx LinkContext) (io.Writer, BlockWriteCommitter, error) {
wr, wrcommit, err := storage.PutStream(lctx.Ctx, store)
return wr, func(lnk datamodel.Link) error {
return wrcommit(lnk.Binary())
}, err
}
}

199
vendor/github.com/ipld/go-ipld-prime/linking/types.go generated vendored Normal file
View File

@@ -0,0 +1,199 @@
package linking
import (
"context"
"hash"
"io"
"github.com/ipld/go-ipld-prime/codec"
"github.com/ipld/go-ipld-prime/datamodel"
)
// LinkSystem is a struct that composes all the individual functions
// needed to load and store content addressed data using IPLD --
// encoding functions, hashing functions, and storage connections --
// and then offers the operations a user wants -- Store and Load -- as methods.
//
// Typically, the functions which are fields of LinkSystem are not used
// directly by users (except to set them, when creating the LinkSystem),
// and it's the higher level operations such as Store and Load that user code then calls.
//
// The most typical way to get a LinkSystem is from the linking/cid package,
// which has a factory function called DefaultLinkSystem.
// The LinkSystem returned by that function will be based on CIDs,
// and use the multicodec registry and multihash registry to select encodings and hashing mechanisms.
// The BlockWriteOpener and BlockReadOpener must still be provided by the user;
// otherwise, only the ComputeLink method will work.
//
// Some implementations of BlockWriteOpener and BlockReadOpener may be
// found in the storage package. Applications are also free to write their own.
// Custom wrapping of BlockWriteOpener and BlockReadOpener are also common,
// and may be reasonable if one wants to build application features that are block-aware.
type LinkSystem struct {
EncoderChooser func(datamodel.LinkPrototype) (codec.Encoder, error)
DecoderChooser func(datamodel.Link) (codec.Decoder, error)
HasherChooser func(datamodel.LinkPrototype) (hash.Hash, error)
StorageWriteOpener BlockWriteOpener
StorageReadOpener BlockReadOpener
TrustedStorage bool
NodeReifier NodeReifier
KnownReifiers map[string]NodeReifier
}
// The following three types are the key functionality we need from a "blockstore".
//
// Some libraries might provide a "blockstore" object that has these as methods;
// it may also have more methods (like enumeration features, GC features, etc),
// but IPLD doesn't generally concern itself with those.
// We just need these key things, so we can "put" and "get".
//
// The functions are a tad more complicated than "put" and "get" so that they have good mechanical sympathy.
// In particular, the writing/"put" side is broken into two phases, so that the abstraction
// makes it easy to begin to write data before the hash that will identify it is fully computed.
type (
// BlockReadOpener defines the shape of a function used to
// open a reader for a block of data.
//
// In a content-addressed system, the Link parameter should be only
// determiner of what block body is returned.
//
// The LinkContext may be zero, or may be used to carry extra information:
// it may be used to carry info which hints at different storage pools;
// it may be used to carry authentication data; etc.
// (Any such behaviors are something that a BlockReadOpener implementation
// will needs to document at a higher detail level than this interface specifies.
// In this interface, we can only note that it is possible to pass such information opaquely
// via the LinkContext or by attachments to the general-purpose Context it contains.)
// The LinkContext should not have effect on the block body returned, however;
// at most should only affect data availability
// (e.g. whether any block body is returned, versus an error).
//
// Reads are cancellable by cancelling the LinkContext.Context.
//
// Other parts of the IPLD library suite (such as the traversal package, and all its functions)
// will typically take a Context as a parameter or piece of config from the caller,
// and will pass that down through the LinkContext, meaning this can be used to
// carry information as well as cancellation control all the way through the system.
//
// BlockReadOpener is typically not used directly, but is instead
// composed in a LinkSystem and used via the methods of LinkSystem.
// LinkSystem methods will helpfully handle the entire process of opening block readers,
// verifying the hash of the data stream, and applying a Decoder to build Nodes -- all as one step.
//
// BlockReadOpener implementations are not required to validate that
// the contents which will be streamed out of the reader actually match
// and hash in the Link parameter before returning.
// (This is something that the LinkSystem composition will handle if you're using it.)
//
// BlockReadOpener can also be created out of storage.ReadableStorage and attached to a LinkSystem
// via the LinkSystem.SetReadStorage method.
//
// Users of a BlockReadOpener function should also check the io.Reader
// for matching the io.Closer interface, and use the Close function as appropriate if present.
BlockReadOpener func(LinkContext, datamodel.Link) (io.Reader, error)
// BlockWriteOpener defines the shape of a function used to open a writer
// into which data can be streamed, and which will eventually be "commited".
// Committing is done using the BlockWriteCommitter returned by using the BlockWriteOpener,
// and finishes the write along with requiring stating the Link which should identify this data for future reading.
//
// The LinkContext may be zero, or may be used to carry extra information:
// it may be used to carry info which hints at different storage pools;
// it may be used to carry authentication data; etc.
//
// Writes are cancellable by cancelling the LinkContext.Context.
//
// Other parts of the IPLD library suite (such as the traversal package, and all its functions)
// will typically take a Context as a parameter or piece of config from the caller,
// and will pass that down through the LinkContext, meaning this can be used to
// carry information as well as cancellation control all the way through the system.
//
// BlockWriteOpener is typically not used directly, but is instead
// composed in a LinkSystem and used via the methods of LinkSystem.
// LinkSystem methods will helpfully handle the entire process of traversing a Node tree,
// encoding this data, hashing it, streaming it to the writer, and committing it -- all as one step.
//
// BlockWriteOpener implementations are expected to start writing their content immediately,
// and later, the returned BlockWriteCommitter should also be able to expect that
// the Link which it is given is a reasonable hash of the content.
// (To give an example of how this might be efficiently implemented:
// One might imagine that if implementing a disk storage mechanism,
// the io.Writer returned from a BlockWriteOpener will be writing a new tempfile,
// and when the BlockWriteCommiter is called, it will flush the writes
// and then use a rename operation to place the tempfile in a permanent path based the Link.)
//
// BlockWriteOpener can also be created out of storage.WritableStorage and attached to a LinkSystem
// via the LinkSystem.SetWriteStorage method.
BlockWriteOpener func(LinkContext) (io.Writer, BlockWriteCommitter, error)
// BlockWriteCommitter defines the shape of a function which, together
// with BlockWriteOpener, handles the writing and "committing" of a write
// to a content-addressable storage system.
//
// BlockWriteCommitter is a function which is will be called at the end of a write process.
// It should flush any buffers and close the io.Writer which was
// made available earlier from the BlockWriteOpener call that also returned this BlockWriteCommitter.
//
// BlockWriteCommitter takes a Link parameter.
// This Link is expected to be a reasonable hash of the content,
// so that the BlockWriteCommitter can use this to commit the data to storage
// in a content-addressable fashion.
// See the documentation of BlockWriteOpener for more description of this
// and an example of how this is likely to be reduced to practice.
BlockWriteCommitter func(datamodel.Link) error
// NodeReifier defines the shape of a function that given a node with no schema
// or a basic schema, constructs Advanced Data Layout node
//
// The LinkSystem itself is passed to the NodeReifier along with a link context
// because Node interface methods on an ADL may actually traverse links to other
// pieces of context addressed data that need to be loaded with the Link system
//
// A NodeReifier return one of three things:
// - original node, no error = no reification occurred, just use original node
// - reified node, no error = the simple node was converted to an ADL
// - nil, error = the simple node should have been converted to an ADL but something
// went wrong when we tried to do so
//
NodeReifier func(LinkContext, datamodel.Node, *LinkSystem) (datamodel.Node, error)
)
// LinkContext is a structure carrying ancilary information that may be used
// while loading or storing data -- see its usage in BlockReadOpener, BlockWriteOpener,
// and in the methods on LinkSystem which handle loading and storing data.
//
// A zero value for LinkContext is generally acceptable in any functions that use it.
// In this case, any operations that need a context.Context will quietly use Context.Background
// (thus being uncancellable) and simply have no additional information to work with.
type LinkContext struct {
// Ctx is the familiar golang Context pattern.
// Use this for cancellation, or attaching additional info
// (for example, perhaps to pass auth tokens through to the storage functions).
Ctx context.Context
// Path where the link was encountered. May be zero.
//
// Functions in the traversal package will set this automatically.
LinkPath datamodel.Path
// When traversing data or encoding: the Node containing the link --
// it may have additional type info, etc, that can be accessed.
// When building / decoding: not present.
//
// Functions in the traversal package will set this automatically.
LinkNode datamodel.Node
// When building data or decoding: the NodeAssembler that will be receiving the link --
// it may have additional type info, etc, that can be accessed.
// When traversing / encoding: not present.
//
// Functions in the traversal package will set this automatically.
LinkNodeAssembler datamodel.NodeAssembler
// Parent of the LinkNode. May be zero.
//
// Functions in the traversal package will set this automatically.
ParentNode datamodel.Node
// REVIEW: ParentNode in LinkContext -- so far, this has only ever been hypothetically useful. Keep or drop?
}

View File

@@ -0,0 +1,117 @@
package multicodec
import (
"github.com/ipld/go-ipld-prime/codec"
)
// DefaultRegistry is a multicodec.Registry instance which is global to the program,
// and is used as a default set of codecs.
//
// Some systems (for example, cidlink.DefaultLinkSystem) will use this default registry,
// which makes it easier to write programs that pass fewer explicit arguments around.
// However, these are *only* for default behaviors;
// variations of functions which allow explicit non-default options should always be available
// (for example, cidlink also has other LinkSystem constructor functions which accept an explicit multicodec.Registry,
// and the LookupEncoder and LookupDecoder functions in any LinkSystem can be replaced).
//
// Since this registry is global, mind that there are also some necessary tradeoffs and limitations:
// It can be difficult to control exactly what's present in this global registry
// (Libraries may register codecs in this registry as a side-effect of importing, so even transitive dependencies can affect its content!).
// Also, this registry is only considered safe to modify at package init time.
// If these are concerns for your program, you can create your own multicodec.Registry values,
// and eschew using the global default.
var DefaultRegistry = Registry{}
// RegisterEncoder updates the global DefaultRegistry to map a multicodec indicator number to the given codec.Encoder function.
// The encoder functions registered can be subsequently looked up using LookupEncoder.
// It is a shortcut to the RegisterEncoder method on the global DefaultRegistry.
//
// Packages which implement an IPLD codec and have a multicodec number associated with them
// are encouraged to register themselves at package init time using this function.
// (Doing this at package init time ensures the default global registry is populated
// without causing race conditions for application code.)
//
// No effort is made to detect conflicting registrations in this map.
// If your dependency tree is such that this becomes a problem,
// there are two ways to address this:
// If RegisterEncoder is called with the same indicator code more than once, the last call wins.
// In practice, this means that if an application has a strong opinion about what implementation for a certain codec,
// then this can be done by making a Register call with that effect at init time in the application's main package.
// This should have the desired effect because the root of the import tree has its init time effect last.
// Alternatively, one can just avoid use of this registry entirely:
// do this by making a LinkSystem that uses a custom EncoderChooser function.
func RegisterEncoder(indicator uint64, encodeFunc codec.Encoder) {
DefaultRegistry.RegisterEncoder(indicator, encodeFunc)
}
// LookupEncoder yields a codec.Encoder function matching a multicodec indicator code number.
// It is a shortcut to the LookupEncoder method on the global DefaultRegistry.
//
// To be available from this lookup function, an encoder must have been registered
// for this indicator number by an earlier call to the RegisterEncoder function.
func LookupEncoder(indicator uint64) (codec.Encoder, error) {
return DefaultRegistry.LookupEncoder(indicator)
}
// ListEncoders returns a list of multicodec indicators for which a codec.Encoder is registered.
// The list is in no particular order.
// It is a shortcut to the ListEncoders method on the global DefaultRegistry.
//
// Be judicious about trying to use this function outside of debugging.
// Because the global default registry is global and easily modified,
// and can be changed by any of the transitive dependencies of your program,
// its contents are not particularly stable.
// In particular, it is not recommended to make any behaviors of your program conditional
// based on information returned by this function -- if your program needs conditional
// behavior based on registred codecs, you may want to consider taking more explicit control
// and using your own non-default registry.
func ListEncoders() []uint64 {
return DefaultRegistry.ListEncoders()
}
// RegisterDecoder updates the global DefaultRegistry a map a multicodec indicator number to the given codec.Decoder function.
// The decoder functions registered can be subsequently looked up using LookupDecoder.
// It is a shortcut to the RegisterDecoder method on the global DefaultRegistry.
//
// Packages which implement an IPLD codec and have a multicodec number associated with them
// are encouraged to register themselves in this map at package init time.
// (Doing this at package init time ensures the default global registry is populated
// without causing race conditions for application code.)
//
// No effort is made to detect conflicting registrations in this map.
// If your dependency tree is such that this becomes a problem,
// there are two ways to address this:
// If RegisterDecoder is called with the same indicator code more than once, the last call wins.
// In practice, this means that if an application has a strong opinion about what implementation for a certain codec,
// then this can be done by making a Register call with that effect at init time in the application's main package.
// This should have the desired effect because the root of the import tree has its init time effect last.
// Alternatively, one can just avoid use of this registry entirely:
// do this by making a LinkSystem that uses a custom DecoderChooser function.
func RegisterDecoder(indicator uint64, decodeFunc codec.Decoder) {
DefaultRegistry.RegisterDecoder(indicator, decodeFunc)
}
// LookupDecoder yields a codec.Decoder function matching a multicodec indicator code number.
// It is a shortcut to the LookupDecoder method on the global DefaultRegistry.
//
// To be available from this lookup function, an decoder must have been registered
// for this indicator number by an earlier call to the RegisterDecoder function.
func LookupDecoder(indicator uint64) (codec.Decoder, error) {
return DefaultRegistry.LookupDecoder(indicator)
}
// ListDecoders returns a list of multicodec indicators for which a codec.Decoder is registered.
// The list is in no particular order.
// It is a shortcut to the ListDecoders method on the global DefaultRegistry.
//
// Be judicious about trying to use this function outside of debugging.
// Because the global default registry is global and easily modified,
// and can be changed by any of the transitive dependencies of your program,
// its contents are not particularly stable.
// In particular, it is not recommended to make any behaviors of your program conditional
// based on information returned by this function -- if your program needs conditional
// behavior based on registred codecs, you may want to consider taking more explicit control
// and using your own non-default registry.
func ListDecoders() []uint64 {
return DefaultRegistry.ListDecoders()
}

View File

@@ -0,0 +1,105 @@
package multicodec
import (
"fmt"
"github.com/ipld/go-ipld-prime/codec"
)
// Registry is a structure for storing mappings of multicodec indicator numbers to codec.Encoder and codec.Decoder functions.
//
// The most typical usage of this structure is in combination with a codec.LinkSystem.
// For example, a linksystem using CIDs and a custom multicodec registry can be constructed
// using cidlink.LinkSystemUsingMulticodecRegistry.
//
// Registry includes no mutexing. If using Registry in a concurrent context, you must handle synchronization yourself.
// (Typically, it is recommended to do initialization earlier in a program, before fanning out goroutines;
// this avoids the need for mutexing overhead.)
//
// go-ipld also has a default registry, which has the same methods as this structure, but are at package scope.
// Some systems, like cidlink.DefaultLinkSystem, will use this default registry.
// However, this default registry is global to the entire program.
// This Registry type is for helping if you wish to make your own registry which does not share that global state.
//
// Multicodec indicator numbers are specified in
// https://github.com/multiformats/multicodec/blob/master/table.csv .
// You should not use indicator numbers which are not specified in that table
// (however, there is nothing in this implementation that will attempt to stop you, either; please behave).
type Registry struct {
encoders map[uint64]codec.Encoder
decoders map[uint64]codec.Decoder
}
func (r *Registry) ensureInit() {
if r.encoders != nil {
return
}
r.encoders = make(map[uint64]codec.Encoder)
r.decoders = make(map[uint64]codec.Decoder)
}
// RegisterEncoder updates a simple map of multicodec indicator number to codec.Encoder function.
// The encoder functions registered can be subsequently looked up using LookupEncoder.
func (r *Registry) RegisterEncoder(indicator uint64, encodeFunc codec.Encoder) {
r.ensureInit()
if encodeFunc == nil {
panic("not sensible to attempt to register a nil function")
}
r.encoders[indicator] = encodeFunc
}
// LookupEncoder yields a codec.Encoder function matching a multicodec indicator code number.
//
// To be available from this lookup function, an encoder must have been registered
// for this indicator number by an earlier call to the RegisterEncoder function.
func (r *Registry) LookupEncoder(indicator uint64) (codec.Encoder, error) {
encodeFunc, exists := r.encoders[indicator]
if !exists {
return nil, fmt.Errorf("no encoder registered for multicodec code %d (0x%x)", indicator, indicator)
}
return encodeFunc, nil
}
// ListEncoders returns a list of multicodec indicators for which a codec.Encoder is registered.
// The list is in no particular order.
func (r *Registry) ListEncoders() []uint64 {
encoders := make([]uint64, 0, len(r.encoders))
for e := range r.encoders {
encoders = append(encoders, e)
}
return encoders
}
// TODO(mvdan): turn most of these uint64s into multicodec.Code
// RegisterDecoder updates a simple map of multicodec indicator number to codec.Decoder function.
// The decoder functions registered can be subsequently looked up using LookupDecoder.
func (r *Registry) RegisterDecoder(indicator uint64, decodeFunc codec.Decoder) {
r.ensureInit()
if decodeFunc == nil {
panic("not sensible to attempt to register a nil function")
}
r.decoders[indicator] = decodeFunc
}
// LookupDecoder yields a codec.Decoder function matching a multicodec indicator code number.
//
// To be available from this lookup function, an decoder must have been registered
// for this indicator number by an earlier call to the RegisterDecoder function.
func (r *Registry) LookupDecoder(indicator uint64) (codec.Decoder, error) {
decodeFunc, exists := r.decoders[indicator]
if !exists {
return nil, fmt.Errorf("no decoder registered for multicodec code %d (0x%x)", indicator, indicator)
}
return decodeFunc, nil
}
// ListDecoders returns a list of multicodec indicators for which a codec.Decoder is registered.
// The list is in no particular order.
func (r *Registry) ListDecoders() []uint64 {
decoders := make([]uint64, 0, len(r.decoders))
for d := range r.decoders {
decoders = append(decoders, d)
}
return decoders
}

View File

@@ -0,0 +1,36 @@
// This is a transitional package: please move your references to `node/basicnode`.
// The new package is identical: we've renamed the import path only.
//
// All content in this package is a thin wrapper around `node/basicnode`.
// Please update at your earliest convenience.
//
// This package will eventually be removed.
package basicnode
import (
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/linking"
"github.com/ipld/go-ipld-prime/node/basicnode"
)
var Prototype = basicnode.Prototype
func Chooser(_ datamodel.Link, _ linking.LinkContext) (datamodel.NodePrototype, error) {
return basicnode.Chooser(nil, linking.LinkContext{})
}
func NewBool(value bool) datamodel.Node { return basicnode.NewBool(value) }
func NewBytes(value []byte) datamodel.Node { return basicnode.NewBytes(value) }
func NewFloat(value float64) datamodel.Node { return basicnode.NewFloat(value) }
func NewInt(value int64) datamodel.Node { return basicnode.NewInt(value) }
func NewLink(value datamodel.Link) datamodel.Node { return basicnode.NewLink(value) }
func NewString(value string) datamodel.Node { return basicnode.NewString(value) }
type Prototype__Any = basicnode.Prototype__Any
type Prototype__Bool = basicnode.Prototype__Bool
type Prototype__Bytes = basicnode.Prototype__Bytes
type Prototype__Float = basicnode.Prototype__Float
type Prototype__Int = basicnode.Prototype__Int
type Prototype__Link = basicnode.Prototype__Link
type Prototype__List = basicnode.Prototype__List
type Prototype__Map = basicnode.Prototype__Map
type Prototype__String = basicnode.Prototype__String

View File

@@ -0,0 +1,146 @@
hackme
======
Design rationale are documented here.
This doc is not necessary reading for users of this package,
but if you're considering submitting patches -- or just trying to understand
why it was written this way, and check for reasoning that might be dated --
then it might be useful reading.
### scalars are just typedefs
This is noteworthy because in codegen, this is typically *not* the case:
in codegen, even scalar types are boxed in a struct, such that it prevents
casting values into those types.
This casting is not a concern for the node implementations in this package, because
- A) we don't have any kind of validation rules to make such casting worrying; and
- B) since our types are unexported, casting is still blocked by this anyway.
### about builders for scalars
The assembler types for scalars (string, int, etc) are pretty funny-looking.
You might wish to make them work without any state at all!
The reason this doesn't fly is that we have to keep the "wip" value in hand
just long enough to return it from the `NodeBuilder.Build` method -- the
`NodeAssembler` contract for `Assign*` methods doesn't permit just returning
their results immediately.
(Another possible reason is if we expected to use these assemblers on
slab-style allocations (say, `[]plainString`)...
however, this is inapplicable at present, because
A) we don't (except places that have special-case internal paths anyway); and
B) the types aren't exported, so users can't either.)
Does this mean that using `NodeBuilder` for scalars has a completely
unnecessary second allocation, which is laughably inefficient? Yes.
It's unfortunate the interfaces constrain us to this.
**But**: one typically doesn't actually use builders for scalars much;
they're just here for completeness.
So this is less of a problem in practice than it might at first seem.
More often, one will use the "any" builder (which is has a whole different set
of design constraints and tradeoffs);
or, if one is writing code and knows which scalar they need, the exported
direct constructor function for that kind
(e.g., `String("foo")` instead of `Prototype__String{}.NewBuilder().AssignString("foo")`)
will do the right thing and do it in one allocation (and it's less to type, too).
### maps and list keyAssembler and valueAssemblers have custom scalar handling
Related to the above heading.
Maps and lists in this package do their own internal handling of scalars,
using unexported features inside the package, because they can more efficient.
### when to invalidate the 'w' pointers
The 'w' pointer -- short for 'wip' node pointer -- has an interesting lifecycle.
In a NodeAssembler, the 'w' pointer should be intialized before the assembler is used.
This means either the matching NodeBuilder type does so; or,
if we're inside recursive structure, the parent assembler did so.
The 'w' pointer is used throughout the life of the assembler.
Setting the 'w' pointer to nil is one of two mechanisms used internally
to mark that assembly has become "finished" (the other mechanism is using
an internal state enum field).
Setting the 'w' pointer to nil has two advantages:
one is that it makes it *impossible* to continue to mutate the target node;
the other is that we need no *additional* memory to track this state change.
However, we can't use the strategy of nilling 'w' in all cases: in particular,
when in the NodeBuilder at the root of some construction,
we need to continue to hold onto the node between when it becomes "finished"
and when Build is called; otherwise we can't actually return the value!
Different stratgies are therefore used in different parts of this package.
Maps and lists use an internal state enum, because they already have one,
and so they might as well; there's no additional cost to this.
Since they can use this state to guard against additional mutations after "finish",
the map and list assemblers don't bother to nil their own 'w' at all.
During recursion to assemble values _inside_ maps and lists, it's interesting:
the child assembler wrapper type takes reponsibility for nilling out
the 'w' pointer in the child assembler's state, doing this at the same time as
it updates the parent's state machine to clear proceeding with the next entry.
In the case of scalars at the root of a build, we took a shortcut:
we actually don't fence against repeat mutations at all.
*You can actually use the assign method more than once*.
We can do this without breaking safety contracts because the scalars
all have a pass-by-value phase somewhere in their lifecycle
(calling `nb.AssignString("x")`, then `n := nb.Build()`, then `nb.AssignString("y")`
won't error if `nb` is a freestanding builder for strings... but it also
won't result in mutating `n` to contain `"y"`, so overall, it's safe).
We could normalize the case with scalars at the root of a tree so that they
error more aggressively... but currently we haven't bothered, since this would
require adding another piece of memory to the scalar builders; and meanwhile
we're not in trouble on compositional correctness.
Note that these remarks are for the `basicnode` package, but may also
apply to other implementations too (e.g., our codegen output follows similar
overall logic).
### NodePrototypes are available through a singleton
Every NodePrototype available from this package is exposed as a field
in a struct of which there's one public exported instance available,
called 'Prototype'.
This means you can use it like this:
```go
nbm := basicnode.Prototype.Map.NewBuilder()
nbs := basicnode.Prototype.String.NewBuilder()
nba := basicnode.Prototype.Any.NewBuilder()
// etc
```
(If you're interested in the performance of this: it's free!
Methods called at the end of the chain are inlinable.
Since all of the types of the structures on the way there are zero-member
structs, the compiler can effectively treat them as constants,
and thus freely elide any memory dereferences that would
otherwise be necessary to get methods on such a value.)
### NodePrototypes are (also) available as exported concrete types
The 'Prototype' singleton is one way to access the NodePrototype in this package;
their exported types are another equivalent way.
```go
basicnode.Prototype.Map = basicnode.Prototype.Map
```
It is recommended to use the singleton style;
they compile to identical assembly, and the singleton is syntactically prettier.
We may make these concrete types unexported in the future.
A decision on this is deferred until some time has passed and
we can accumulate reasonable certainty that there's no need for an exported type
(such as type assertions, etc).

View File

@@ -0,0 +1,197 @@
package basicnode
import (
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/linking"
)
var (
//_ datamodel.Node = &anyNode{}
_ datamodel.NodePrototype = Prototype__Any{}
_ datamodel.NodeBuilder = &anyBuilder{}
//_ datamodel.NodeAssembler = &anyAssembler{}
)
// Note that we don't use a "var _" declaration to assert that Chooser
// implements traversal.LinkTargetNodePrototypeChooser, to keep basicnode's
// dependencies fairly light.
// Chooser implements traversal.LinkTargetNodePrototypeChooser.
//
// It can be used directly when loading links into the "any" prototype,
// or with another chooser layer on top, such as:
//
// prototypeChooser := dagpb.AddSupportToChooser(basicnode.Chooser)
func Chooser(_ datamodel.Link, _ linking.LinkContext) (datamodel.NodePrototype, error) {
return Prototype.Any, nil
}
// -- Node interface methods -->
// Unimplemented at present -- see "REVIEW" comment on anyNode.
// -- NodePrototype -->
type Prototype__Any struct{}
func (Prototype__Any) NewBuilder() datamodel.NodeBuilder {
return &anyBuilder{}
}
// -- NodeBuilder -->
// anyBuilder is a builder for any kind of node.
//
// anyBuilder is a little unusual in its internal workings:
// unlike most builders, it doesn't embed the corresponding assembler,
// nor will it end up using anyNode,
// but instead embeds a builder for each of the kinds it might contain.
// This is because we want a more granular return at the end:
// if we used anyNode, and returned a pointer to just the relevant part of it,
// we'd have all the extra bytes of anyNode still reachable in GC terms
// for as long as that handle to the interior of it remains live.
type anyBuilder struct {
// kind is set on first interaction, and used to select which builder to delegate 'Build' to!
// As soon as it's been set to a value other than zero (being "Invalid"), all other Assign/Begin calls will fail since something is already in progress.
// May also be set to the magic value '99', which means "i dunno, I'm just carrying another node of unknown prototype".
kind datamodel.Kind
// Only one of the following ends up being used...
// but we don't know in advance which one, so all are embeded here.
// This uses excessive space, but amortizes allocations, and all will be
// freed as soon as the builder is done.
// Builders are only used for recursives;
// scalars are simple enough we just do them directly.
// 'scalarNode' may also hold another Node of unknown prototype (possibly not even from this package),
// in which case this is indicated by 'kind==99'.
mapBuilder plainMap__Builder
listBuilder plainList__Builder
scalarNode datamodel.Node
}
func (nb *anyBuilder) Reset() {
*nb = anyBuilder{}
}
func (nb *anyBuilder) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_Map
nb.mapBuilder.w = &plainMap{}
return nb.mapBuilder.BeginMap(sizeHint)
}
func (nb *anyBuilder) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_List
nb.listBuilder.w = &plainList{}
return nb.listBuilder.BeginList(sizeHint)
}
func (nb *anyBuilder) AssignNull() error {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_Null
return nil
}
func (nb *anyBuilder) AssignBool(v bool) error {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_Bool
nb.scalarNode = NewBool(v)
return nil
}
func (nb *anyBuilder) AssignInt(v int64) error {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_Int
nb.scalarNode = NewInt(v)
return nil
}
func (nb *anyBuilder) AssignFloat(v float64) error {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_Float
nb.scalarNode = NewFloat(v)
return nil
}
func (nb *anyBuilder) AssignString(v string) error {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_String
nb.scalarNode = NewString(v)
return nil
}
func (nb *anyBuilder) AssignBytes(v []byte) error {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_Bytes
nb.scalarNode = NewBytes(v)
return nil
}
func (nb *anyBuilder) AssignLink(v datamodel.Link) error {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_Link
nb.scalarNode = NewLink(v)
return nil
}
func (nb *anyBuilder) AssignNode(v datamodel.Node) error {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = 99
nb.scalarNode = v
return nil
}
func (anyBuilder) Prototype() datamodel.NodePrototype {
return Prototype.Any
}
func (nb *anyBuilder) Build() datamodel.Node {
switch nb.kind {
case datamodel.Kind_Invalid:
panic("misuse")
case datamodel.Kind_Map:
return nb.mapBuilder.Build()
case datamodel.Kind_List:
return nb.listBuilder.Build()
case datamodel.Kind_Null:
return datamodel.Null
case datamodel.Kind_Bool:
return nb.scalarNode
case datamodel.Kind_Int:
return nb.scalarNode
case datamodel.Kind_Float:
return nb.scalarNode
case datamodel.Kind_String:
return nb.scalarNode
case datamodel.Kind_Bytes:
return nb.scalarNode
case datamodel.Kind_Link:
return nb.scalarNode
case 99:
return nb.scalarNode
default:
panic("unreachable")
}
}
// -- NodeAssembler -->
// ... oddly enough, we seem to be able to put off implementing this
// until we also implement something that goes full-hog on amortization
// and actually has a slab of `anyNode`. Which so far, nothing does.
// See "REVIEW" comment on anyNode.
// type anyAssembler struct {
// w *anyNode
// }

View File

@@ -0,0 +1,144 @@
package basicnode
import (
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/mixins"
)
var (
_ datamodel.Node = plainBool(false)
_ datamodel.NodePrototype = Prototype__Bool{}
_ datamodel.NodeBuilder = &plainBool__Builder{}
_ datamodel.NodeAssembler = &plainBool__Assembler{}
)
func NewBool(value bool) datamodel.Node {
v := plainBool(value)
return &v
}
// plainBool is a simple boxed boolean that complies with datamodel.Node.
type plainBool bool
// -- Node interface methods -->
func (plainBool) Kind() datamodel.Kind {
return datamodel.Kind_Bool
}
func (plainBool) LookupByString(string) (datamodel.Node, error) {
return mixins.Bool{TypeName: "bool"}.LookupByString("")
}
func (plainBool) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return mixins.Bool{TypeName: "bool"}.LookupByNode(nil)
}
func (plainBool) LookupByIndex(idx int64) (datamodel.Node, error) {
return mixins.Bool{TypeName: "bool"}.LookupByIndex(0)
}
func (plainBool) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
return mixins.Bool{TypeName: "bool"}.LookupBySegment(seg)
}
func (plainBool) MapIterator() datamodel.MapIterator {
return nil
}
func (plainBool) ListIterator() datamodel.ListIterator {
return nil
}
func (plainBool) Length() int64 {
return -1
}
func (plainBool) IsAbsent() bool {
return false
}
func (plainBool) IsNull() bool {
return false
}
func (n plainBool) AsBool() (bool, error) {
return bool(n), nil
}
func (plainBool) AsInt() (int64, error) {
return mixins.Bool{TypeName: "bool"}.AsInt()
}
func (plainBool) AsFloat() (float64, error) {
return mixins.Bool{TypeName: "bool"}.AsFloat()
}
func (plainBool) AsString() (string, error) {
return mixins.Bool{TypeName: "bool"}.AsString()
}
func (plainBool) AsBytes() ([]byte, error) {
return mixins.Bool{TypeName: "bool"}.AsBytes()
}
func (plainBool) AsLink() (datamodel.Link, error) {
return mixins.Bool{TypeName: "bool"}.AsLink()
}
func (plainBool) Prototype() datamodel.NodePrototype {
return Prototype__Bool{}
}
// -- NodePrototype -->
type Prototype__Bool struct{}
func (Prototype__Bool) NewBuilder() datamodel.NodeBuilder {
var w plainBool
return &plainBool__Builder{plainBool__Assembler{w: &w}}
}
// -- NodeBuilder -->
type plainBool__Builder struct {
plainBool__Assembler
}
func (nb *plainBool__Builder) Build() datamodel.Node {
return nb.w
}
func (nb *plainBool__Builder) Reset() {
var w plainBool
*nb = plainBool__Builder{plainBool__Assembler{w: &w}}
}
// -- NodeAssembler -->
type plainBool__Assembler struct {
w *plainBool
}
func (plainBool__Assembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return mixins.BoolAssembler{TypeName: "bool"}.BeginMap(0)
}
func (plainBool__Assembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return mixins.BoolAssembler{TypeName: "bool"}.BeginList(0)
}
func (plainBool__Assembler) AssignNull() error {
return mixins.BoolAssembler{TypeName: "bool"}.AssignNull()
}
func (na *plainBool__Assembler) AssignBool(v bool) error {
*na.w = plainBool(v)
return nil
}
func (plainBool__Assembler) AssignInt(int64) error {
return mixins.BoolAssembler{TypeName: "bool"}.AssignInt(0)
}
func (plainBool__Assembler) AssignFloat(float64) error {
return mixins.BoolAssembler{TypeName: "bool"}.AssignFloat(0)
}
func (plainBool__Assembler) AssignString(string) error {
return mixins.BoolAssembler{TypeName: "bool"}.AssignString("")
}
func (plainBool__Assembler) AssignBytes([]byte) error {
return mixins.BoolAssembler{TypeName: "bool"}.AssignBytes(nil)
}
func (plainBool__Assembler) AssignLink(datamodel.Link) error {
return mixins.BoolAssembler{TypeName: "bool"}.AssignLink(nil)
}
func (na *plainBool__Assembler) AssignNode(v datamodel.Node) error {
if v2, err := v.AsBool(); err != nil {
return err
} else {
*na.w = plainBool(v2)
return nil
}
}
func (plainBool__Assembler) Prototype() datamodel.NodePrototype {
return Prototype__Bool{}
}

View File

@@ -0,0 +1,157 @@
package basicnode
import (
"bytes"
"io"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/mixins"
)
var (
_ datamodel.Node = plainBytes(nil)
_ datamodel.NodePrototype = Prototype__Bytes{}
_ datamodel.NodeBuilder = &plainBytes__Builder{}
_ datamodel.NodeAssembler = &plainBytes__Assembler{}
)
func NewBytes(value []byte) datamodel.Node {
v := plainBytes(value)
return &v
}
// plainBytes is a simple boxed byte slice that complies with datamodel.Node.
type plainBytes []byte
// -- Node interface methods -->
func (plainBytes) Kind() datamodel.Kind {
return datamodel.Kind_Bytes
}
func (plainBytes) LookupByString(string) (datamodel.Node, error) {
return mixins.Bytes{TypeName: "bytes"}.LookupByString("")
}
func (plainBytes) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return mixins.Bytes{TypeName: "bytes"}.LookupByNode(nil)
}
func (plainBytes) LookupByIndex(idx int64) (datamodel.Node, error) {
return mixins.Bytes{TypeName: "bytes"}.LookupByIndex(0)
}
func (plainBytes) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
return mixins.Bytes{TypeName: "bytes"}.LookupBySegment(seg)
}
func (plainBytes) MapIterator() datamodel.MapIterator {
return nil
}
func (plainBytes) ListIterator() datamodel.ListIterator {
return nil
}
func (plainBytes) Length() int64 {
return -1
}
func (plainBytes) IsAbsent() bool {
return false
}
func (plainBytes) IsNull() bool {
return false
}
func (plainBytes) AsBool() (bool, error) {
return mixins.Bytes{TypeName: "bytes"}.AsBool()
}
func (plainBytes) AsInt() (int64, error) {
return mixins.Bytes{TypeName: "bytes"}.AsInt()
}
func (plainBytes) AsFloat() (float64, error) {
return mixins.Bytes{TypeName: "bytes"}.AsFloat()
}
func (plainBytes) AsString() (string, error) {
return mixins.Bytes{TypeName: "bytes"}.AsString()
}
func (n plainBytes) AsBytes() ([]byte, error) {
return []byte(n), nil
}
func (plainBytes) AsLink() (datamodel.Link, error) {
return mixins.Bytes{TypeName: "bytes"}.AsLink()
}
func (plainBytes) Prototype() datamodel.NodePrototype {
return Prototype__Bytes{}
}
func (n plainBytes) AsLargeBytes() (io.ReadSeeker, error) {
return bytes.NewReader(n), nil
}
// -- NodePrototype -->
type Prototype__Bytes struct{}
func (Prototype__Bytes) NewBuilder() datamodel.NodeBuilder {
var w plainBytes
return &plainBytes__Builder{plainBytes__Assembler{w: &w}}
}
// -- NodeBuilder -->
type plainBytes__Builder struct {
plainBytes__Assembler
}
func (nb *plainBytes__Builder) Build() datamodel.Node {
return nb.w
}
func (nb *plainBytes__Builder) Reset() {
var w plainBytes
*nb = plainBytes__Builder{plainBytes__Assembler{w: &w}}
}
// -- NodeAssembler -->
type plainBytes__Assembler struct {
w datamodel.Node
}
func (plainBytes__Assembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return mixins.BytesAssembler{TypeName: "bytes"}.BeginMap(0)
}
func (plainBytes__Assembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return mixins.BytesAssembler{TypeName: "bytes"}.BeginList(0)
}
func (plainBytes__Assembler) AssignNull() error {
return mixins.BytesAssembler{TypeName: "bytes"}.AssignNull()
}
func (plainBytes__Assembler) AssignBool(bool) error {
return mixins.BytesAssembler{TypeName: "bytes"}.AssignBool(false)
}
func (plainBytes__Assembler) AssignInt(int64) error {
return mixins.BytesAssembler{TypeName: "bytes"}.AssignInt(0)
}
func (plainBytes__Assembler) AssignFloat(float64) error {
return mixins.BytesAssembler{TypeName: "bytes"}.AssignFloat(0)
}
func (plainBytes__Assembler) AssignString(string) error {
return mixins.BytesAssembler{TypeName: "bytes"}.AssignString("")
}
func (na *plainBytes__Assembler) AssignBytes(v []byte) error {
na.w = datamodel.Node(plainBytes(v))
return nil
}
func (plainBytes__Assembler) AssignLink(datamodel.Link) error {
return mixins.BytesAssembler{TypeName: "bytes"}.AssignLink(nil)
}
func (na *plainBytes__Assembler) AssignNode(v datamodel.Node) error {
if lb, ok := v.(datamodel.LargeBytesNode); ok {
lbn, err := lb.AsLargeBytes()
if err == nil {
na.w = streamBytes{lbn}
return nil
}
}
if v2, err := v.AsBytes(); err != nil {
return err
} else {
na.w = plainBytes(v2)
return nil
}
}
func (plainBytes__Assembler) Prototype() datamodel.NodePrototype {
return Prototype__Bytes{}
}

View File

@@ -0,0 +1,81 @@
package basicnode
import (
"io"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/mixins"
)
var (
_ datamodel.Node = streamBytes{nil}
_ datamodel.NodePrototype = Prototype__Bytes{}
_ datamodel.NodeBuilder = &plainBytes__Builder{}
_ datamodel.NodeAssembler = &plainBytes__Assembler{}
)
func NewBytesFromReader(rs io.ReadSeeker) datamodel.Node {
return streamBytes{rs}
}
// streamBytes is a boxed reader that complies with datamodel.Node.
type streamBytes struct {
io.ReadSeeker
}
// -- Node interface methods -->
func (streamBytes) Kind() datamodel.Kind {
return datamodel.Kind_Bytes
}
func (streamBytes) LookupByString(string) (datamodel.Node, error) {
return mixins.Bytes{TypeName: "bytes"}.LookupByString("")
}
func (streamBytes) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return mixins.Bytes{TypeName: "bytes"}.LookupByNode(nil)
}
func (streamBytes) LookupByIndex(idx int64) (datamodel.Node, error) {
return mixins.Bytes{TypeName: "bytes"}.LookupByIndex(0)
}
func (streamBytes) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
return mixins.Bytes{TypeName: "bytes"}.LookupBySegment(seg)
}
func (streamBytes) MapIterator() datamodel.MapIterator {
return nil
}
func (streamBytes) ListIterator() datamodel.ListIterator {
return nil
}
func (streamBytes) Length() int64 {
return -1
}
func (streamBytes) IsAbsent() bool {
return false
}
func (streamBytes) IsNull() bool {
return false
}
func (streamBytes) AsBool() (bool, error) {
return mixins.Bytes{TypeName: "bytes"}.AsBool()
}
func (streamBytes) AsInt() (int64, error) {
return mixins.Bytes{TypeName: "bytes"}.AsInt()
}
func (streamBytes) AsFloat() (float64, error) {
return mixins.Bytes{TypeName: "bytes"}.AsFloat()
}
func (streamBytes) AsString() (string, error) {
return mixins.Bytes{TypeName: "bytes"}.AsString()
}
func (n streamBytes) AsBytes() ([]byte, error) {
return io.ReadAll(n)
}
func (streamBytes) AsLink() (datamodel.Link, error) {
return mixins.Bytes{TypeName: "bytes"}.AsLink()
}
func (streamBytes) Prototype() datamodel.NodePrototype {
return Prototype__Bytes{}
}
func (n streamBytes) AsLargeBytes() (io.ReadSeeker, error) {
return n.ReadSeeker, nil
}

View File

@@ -0,0 +1,144 @@
package basicnode
import (
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/mixins"
)
var (
_ datamodel.Node = plainFloat(0)
_ datamodel.NodePrototype = Prototype__Float{}
_ datamodel.NodeBuilder = &plainFloat__Builder{}
_ datamodel.NodeAssembler = &plainFloat__Assembler{}
)
func NewFloat(value float64) datamodel.Node {
v := plainFloat(value)
return &v
}
// plainFloat is a simple boxed float that complies with datamodel.Node.
type plainFloat float64
// -- Node interface methods -->
func (plainFloat) Kind() datamodel.Kind {
return datamodel.Kind_Float
}
func (plainFloat) LookupByString(string) (datamodel.Node, error) {
return mixins.Float{TypeName: "float"}.LookupByString("")
}
func (plainFloat) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return mixins.Float{TypeName: "float"}.LookupByNode(nil)
}
func (plainFloat) LookupByIndex(idx int64) (datamodel.Node, error) {
return mixins.Float{TypeName: "float"}.LookupByIndex(0)
}
func (plainFloat) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
return mixins.Float{TypeName: "float"}.LookupBySegment(seg)
}
func (plainFloat) MapIterator() datamodel.MapIterator {
return nil
}
func (plainFloat) ListIterator() datamodel.ListIterator {
return nil
}
func (plainFloat) Length() int64 {
return -1
}
func (plainFloat) IsAbsent() bool {
return false
}
func (plainFloat) IsNull() bool {
return false
}
func (plainFloat) AsBool() (bool, error) {
return mixins.Float{TypeName: "float"}.AsBool()
}
func (plainFloat) AsInt() (int64, error) {
return mixins.Float{TypeName: "float"}.AsInt()
}
func (n plainFloat) AsFloat() (float64, error) {
return float64(n), nil
}
func (plainFloat) AsString() (string, error) {
return mixins.Float{TypeName: "float"}.AsString()
}
func (plainFloat) AsBytes() ([]byte, error) {
return mixins.Float{TypeName: "float"}.AsBytes()
}
func (plainFloat) AsLink() (datamodel.Link, error) {
return mixins.Float{TypeName: "float"}.AsLink()
}
func (plainFloat) Prototype() datamodel.NodePrototype {
return Prototype__Float{}
}
// -- NodePrototype -->
type Prototype__Float struct{}
func (Prototype__Float) NewBuilder() datamodel.NodeBuilder {
var w plainFloat
return &plainFloat__Builder{plainFloat__Assembler{w: &w}}
}
// -- NodeBuilder -->
type plainFloat__Builder struct {
plainFloat__Assembler
}
func (nb *plainFloat__Builder) Build() datamodel.Node {
return nb.w
}
func (nb *plainFloat__Builder) Reset() {
var w plainFloat
*nb = plainFloat__Builder{plainFloat__Assembler{w: &w}}
}
// -- NodeAssembler -->
type plainFloat__Assembler struct {
w *plainFloat
}
func (plainFloat__Assembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return mixins.FloatAssembler{TypeName: "float"}.BeginMap(0)
}
func (plainFloat__Assembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return mixins.FloatAssembler{TypeName: "float"}.BeginList(0)
}
func (plainFloat__Assembler) AssignNull() error {
return mixins.FloatAssembler{TypeName: "float"}.AssignNull()
}
func (plainFloat__Assembler) AssignBool(bool) error {
return mixins.FloatAssembler{TypeName: "float"}.AssignBool(false)
}
func (plainFloat__Assembler) AssignInt(int64) error {
return mixins.FloatAssembler{TypeName: "float"}.AssignInt(0)
}
func (na *plainFloat__Assembler) AssignFloat(v float64) error {
*na.w = plainFloat(v)
return nil
}
func (plainFloat__Assembler) AssignString(string) error {
return mixins.FloatAssembler{TypeName: "float"}.AssignString("")
}
func (plainFloat__Assembler) AssignBytes([]byte) error {
return mixins.FloatAssembler{TypeName: "float"}.AssignBytes(nil)
}
func (plainFloat__Assembler) AssignLink(datamodel.Link) error {
return mixins.FloatAssembler{TypeName: "float"}.AssignLink(nil)
}
func (na *plainFloat__Assembler) AssignNode(v datamodel.Node) error {
if v2, err := v.AsFloat(); err != nil {
return err
} else {
*na.w = plainFloat(v2)
return nil
}
}
func (plainFloat__Assembler) Prototype() datamodel.NodePrototype {
return Prototype__Float{}
}

View File

@@ -0,0 +1,226 @@
package basicnode
import (
"fmt"
"math"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/mixins"
)
var (
_ datamodel.Node = plainInt(0)
_ datamodel.Node = plainUint(0)
_ datamodel.UintNode = plainUint(0)
_ datamodel.NodePrototype = Prototype__Int{}
_ datamodel.NodeBuilder = &plainInt__Builder{}
_ datamodel.NodeAssembler = &plainInt__Assembler{}
)
func NewInt(value int64) datamodel.Node {
return plainInt(value)
}
// NewUint creates a new uint64-backed Node which will behave as a plain Int
// node but also conforms to the datamodel.UintNode interface which can access
// the full uint64 range.
//
// EXPERIMENTAL: this API is experimental and may be changed or removed in a
// future release.
func NewUint(value uint64) datamodel.Node {
return plainUint(value)
}
// plainInt is a simple boxed int that complies with datamodel.Node.
type plainInt int64
// -- Node interface methods for plainInt -->
func (plainInt) Kind() datamodel.Kind {
return datamodel.Kind_Int
}
func (plainInt) LookupByString(string) (datamodel.Node, error) {
return mixins.Int{TypeName: "int"}.LookupByString("")
}
func (plainInt) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return mixins.Int{TypeName: "int"}.LookupByNode(nil)
}
func (plainInt) LookupByIndex(idx int64) (datamodel.Node, error) {
return mixins.Int{TypeName: "int"}.LookupByIndex(0)
}
func (plainInt) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
return mixins.Int{TypeName: "int"}.LookupBySegment(seg)
}
func (plainInt) MapIterator() datamodel.MapIterator {
return nil
}
func (plainInt) ListIterator() datamodel.ListIterator {
return nil
}
func (plainInt) Length() int64 {
return -1
}
func (plainInt) IsAbsent() bool {
return false
}
func (plainInt) IsNull() bool {
return false
}
func (plainInt) AsBool() (bool, error) {
return mixins.Int{TypeName: "int"}.AsBool()
}
func (n plainInt) AsInt() (int64, error) {
return int64(n), nil
}
func (plainInt) AsFloat() (float64, error) {
return mixins.Int{TypeName: "int"}.AsFloat()
}
func (plainInt) AsString() (string, error) {
return mixins.Int{TypeName: "int"}.AsString()
}
func (plainInt) AsBytes() ([]byte, error) {
return mixins.Int{TypeName: "int"}.AsBytes()
}
func (plainInt) AsLink() (datamodel.Link, error) {
return mixins.Int{TypeName: "int"}.AsLink()
}
func (plainInt) Prototype() datamodel.NodePrototype {
return Prototype__Int{}
}
// plainUint is a simple boxed uint64 that complies with datamodel.Node,
// allowing representation of the uint64 range above the int64 maximum via the
// UintNode interface
type plainUint uint64
// -- Node interface methods for plainUint -->
func (plainUint) Kind() datamodel.Kind {
return datamodel.Kind_Int
}
func (plainUint) LookupByString(string) (datamodel.Node, error) {
return mixins.Int{TypeName: "int"}.LookupByString("")
}
func (plainUint) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return mixins.Int{TypeName: "int"}.LookupByNode(nil)
}
func (plainUint) LookupByIndex(idx int64) (datamodel.Node, error) {
return mixins.Int{TypeName: "int"}.LookupByIndex(0)
}
func (plainUint) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
return mixins.Int{TypeName: "int"}.LookupBySegment(seg)
}
func (plainUint) MapIterator() datamodel.MapIterator {
return nil
}
func (plainUint) ListIterator() datamodel.ListIterator {
return nil
}
func (plainUint) Length() int64 {
return -1
}
func (plainUint) IsAbsent() bool {
return false
}
func (plainUint) IsNull() bool {
return false
}
func (plainUint) AsBool() (bool, error) {
return mixins.Int{TypeName: "int"}.AsBool()
}
func (n plainUint) AsInt() (int64, error) {
if uint64(n) > uint64(math.MaxInt64) {
return -1, fmt.Errorf("unsigned integer out of range of int64 type")
}
return int64(n), nil
}
func (plainUint) AsFloat() (float64, error) {
return mixins.Int{TypeName: "int"}.AsFloat()
}
func (plainUint) AsString() (string, error) {
return mixins.Int{TypeName: "int"}.AsString()
}
func (plainUint) AsBytes() ([]byte, error) {
return mixins.Int{TypeName: "int"}.AsBytes()
}
func (plainUint) AsLink() (datamodel.Link, error) {
return mixins.Int{TypeName: "int"}.AsLink()
}
func (plainUint) Prototype() datamodel.NodePrototype {
return Prototype__Int{}
}
// allows plainUint to conform to the plainUint interface
func (n plainUint) AsUint() (uint64, error) {
return uint64(n), nil
}
// -- NodePrototype -->
type Prototype__Int struct{}
func (Prototype__Int) NewBuilder() datamodel.NodeBuilder {
var w plainInt
return &plainInt__Builder{plainInt__Assembler{w: &w}}
}
// -- NodeBuilder -->
type plainInt__Builder struct {
plainInt__Assembler
}
func (nb *plainInt__Builder) Build() datamodel.Node {
return nb.w
}
func (nb *plainInt__Builder) Reset() {
var w plainInt
*nb = plainInt__Builder{plainInt__Assembler{w: &w}}
}
// -- NodeAssembler -->
type plainInt__Assembler struct {
w *plainInt
}
func (plainInt__Assembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return mixins.IntAssembler{TypeName: "int"}.BeginMap(0)
}
func (plainInt__Assembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return mixins.IntAssembler{TypeName: "int"}.BeginList(0)
}
func (plainInt__Assembler) AssignNull() error {
return mixins.IntAssembler{TypeName: "int"}.AssignNull()
}
func (plainInt__Assembler) AssignBool(bool) error {
return mixins.IntAssembler{TypeName: "int"}.AssignBool(false)
}
func (na *plainInt__Assembler) AssignInt(v int64) error {
*na.w = plainInt(v)
return nil
}
func (plainInt__Assembler) AssignFloat(float64) error {
return mixins.IntAssembler{TypeName: "int"}.AssignFloat(0)
}
func (plainInt__Assembler) AssignString(string) error {
return mixins.IntAssembler{TypeName: "int"}.AssignString("")
}
func (plainInt__Assembler) AssignBytes([]byte) error {
return mixins.IntAssembler{TypeName: "int"}.AssignBytes(nil)
}
func (plainInt__Assembler) AssignLink(datamodel.Link) error {
return mixins.IntAssembler{TypeName: "int"}.AssignLink(nil)
}
func (na *plainInt__Assembler) AssignNode(v datamodel.Node) error {
if v2, err := v.AsInt(); err != nil {
return err
} else {
*na.w = plainInt(v2)
return nil
}
}
func (plainInt__Assembler) Prototype() datamodel.NodePrototype {
return Prototype__Int{}
}

View File

@@ -0,0 +1,145 @@
package basicnode
import (
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/mixins"
)
var (
_ datamodel.Node = &plainLink{}
_ datamodel.NodePrototype = Prototype__Link{}
_ datamodel.NodeBuilder = &plainLink__Builder{}
_ datamodel.NodeAssembler = &plainLink__Assembler{}
)
func NewLink(value datamodel.Link) datamodel.Node {
return &plainLink{value}
}
// plainLink is a simple box around a Link that complies with datamodel.Node.
type plainLink struct {
x datamodel.Link
}
// -- Node interface methods -->
func (plainLink) Kind() datamodel.Kind {
return datamodel.Kind_Link
}
func (plainLink) LookupByString(string) (datamodel.Node, error) {
return mixins.Link{TypeName: "link"}.LookupByString("")
}
func (plainLink) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return mixins.Link{TypeName: "link"}.LookupByNode(nil)
}
func (plainLink) LookupByIndex(idx int64) (datamodel.Node, error) {
return mixins.Link{TypeName: "link"}.LookupByIndex(0)
}
func (plainLink) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
return mixins.Link{TypeName: "link"}.LookupBySegment(seg)
}
func (plainLink) MapIterator() datamodel.MapIterator {
return nil
}
func (plainLink) ListIterator() datamodel.ListIterator {
return nil
}
func (plainLink) Length() int64 {
return -1
}
func (plainLink) IsAbsent() bool {
return false
}
func (plainLink) IsNull() bool {
return false
}
func (plainLink) AsBool() (bool, error) {
return mixins.Link{TypeName: "link"}.AsBool()
}
func (plainLink) AsInt() (int64, error) {
return mixins.Link{TypeName: "link"}.AsInt()
}
func (plainLink) AsFloat() (float64, error) {
return mixins.Link{TypeName: "link"}.AsFloat()
}
func (plainLink) AsString() (string, error) {
return mixins.Link{TypeName: "link"}.AsString()
}
func (plainLink) AsBytes() ([]byte, error) {
return mixins.Link{TypeName: "link"}.AsBytes()
}
func (n *plainLink) AsLink() (datamodel.Link, error) {
return n.x, nil
}
func (plainLink) Prototype() datamodel.NodePrototype {
return Prototype__Link{}
}
// -- NodePrototype -->
type Prototype__Link struct{}
func (Prototype__Link) NewBuilder() datamodel.NodeBuilder {
var w plainLink
return &plainLink__Builder{plainLink__Assembler{w: &w}}
}
// -- NodeBuilder -->
type plainLink__Builder struct {
plainLink__Assembler
}
func (nb *plainLink__Builder) Build() datamodel.Node {
return nb.w
}
func (nb *plainLink__Builder) Reset() {
var w plainLink
*nb = plainLink__Builder{plainLink__Assembler{w: &w}}
}
// -- NodeAssembler -->
type plainLink__Assembler struct {
w *plainLink
}
func (plainLink__Assembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return mixins.LinkAssembler{TypeName: "link"}.BeginMap(0)
}
func (plainLink__Assembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return mixins.LinkAssembler{TypeName: "link"}.BeginList(0)
}
func (plainLink__Assembler) AssignNull() error {
return mixins.LinkAssembler{TypeName: "link"}.AssignNull()
}
func (plainLink__Assembler) AssignBool(bool) error {
return mixins.LinkAssembler{TypeName: "link"}.AssignBool(false)
}
func (plainLink__Assembler) AssignInt(int64) error {
return mixins.LinkAssembler{TypeName: "link"}.AssignInt(0)
}
func (plainLink__Assembler) AssignFloat(float64) error {
return mixins.LinkAssembler{TypeName: "link"}.AssignFloat(0)
}
func (plainLink__Assembler) AssignString(string) error {
return mixins.LinkAssembler{TypeName: "link"}.AssignString("")
}
func (plainLink__Assembler) AssignBytes([]byte) error {
return mixins.LinkAssembler{TypeName: "link"}.AssignBytes(nil)
}
func (na *plainLink__Assembler) AssignLink(v datamodel.Link) error {
na.w.x = v
return nil
}
func (na *plainLink__Assembler) AssignNode(v datamodel.Node) error {
if v2, err := v.AsLink(); err != nil {
return err
} else {
na.w.x = v2
return nil
}
}
func (plainLink__Assembler) Prototype() datamodel.NodePrototype {
return Prototype__Link{}
}

View File

@@ -0,0 +1,360 @@
package basicnode
import (
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/mixins"
)
var (
_ datamodel.Node = &plainList{}
_ datamodel.NodePrototype = Prototype__List{}
_ datamodel.NodeBuilder = &plainList__Builder{}
_ datamodel.NodeAssembler = &plainList__Assembler{}
)
// plainList is a concrete type that provides a list-kind datamodel.Node.
// It can contain any kind of value.
// plainList is also embedded in the 'any' struct and usable from there.
type plainList struct {
x []datamodel.Node
}
// -- Node interface methods -->
func (plainList) Kind() datamodel.Kind {
return datamodel.Kind_List
}
func (plainList) LookupByString(string) (datamodel.Node, error) {
return mixins.List{TypeName: "list"}.LookupByString("")
}
func (plainList) LookupByNode(datamodel.Node) (datamodel.Node, error) {
return mixins.List{TypeName: "list"}.LookupByNode(nil)
}
func (n *plainList) LookupByIndex(idx int64) (datamodel.Node, error) {
if n.Length() <= idx {
return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfInt(idx)}
}
return n.x[idx], nil
}
func (n *plainList) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
idx, err := seg.Index()
if err != nil {
return nil, datamodel.ErrInvalidSegmentForList{TroubleSegment: seg, Reason: err}
}
return n.LookupByIndex(idx)
}
func (plainList) MapIterator() datamodel.MapIterator {
return nil
}
func (n *plainList) ListIterator() datamodel.ListIterator {
return &plainList_ListIterator{n, 0}
}
func (n *plainList) Length() int64 {
return int64(len(n.x))
}
func (plainList) IsAbsent() bool {
return false
}
func (plainList) IsNull() bool {
return false
}
func (plainList) AsBool() (bool, error) {
return mixins.List{TypeName: "list"}.AsBool()
}
func (plainList) AsInt() (int64, error) {
return mixins.List{TypeName: "list"}.AsInt()
}
func (plainList) AsFloat() (float64, error) {
return mixins.List{TypeName: "list"}.AsFloat()
}
func (plainList) AsString() (string, error) {
return mixins.List{TypeName: "list"}.AsString()
}
func (plainList) AsBytes() ([]byte, error) {
return mixins.List{TypeName: "list"}.AsBytes()
}
func (plainList) AsLink() (datamodel.Link, error) {
return mixins.List{TypeName: "list"}.AsLink()
}
func (plainList) Prototype() datamodel.NodePrototype {
return Prototype.List
}
type plainList_ListIterator struct {
n *plainList
idx int
}
func (itr *plainList_ListIterator) Next() (idx int64, v datamodel.Node, _ error) {
if itr.Done() {
return -1, nil, datamodel.ErrIteratorOverread{}
}
v = itr.n.x[itr.idx]
idx = int64(itr.idx)
itr.idx++
return
}
func (itr *plainList_ListIterator) Done() bool {
return itr.idx >= len(itr.n.x)
}
// -- NodePrototype -->
type Prototype__List struct{}
func (Prototype__List) NewBuilder() datamodel.NodeBuilder {
return &plainList__Builder{plainList__Assembler{w: &plainList{}}}
}
// -- NodeBuilder -->
type plainList__Builder struct {
plainList__Assembler
}
func (nb *plainList__Builder) Build() datamodel.Node {
if nb.state != laState_finished {
panic("invalid state: assembler must be 'finished' before Build can be called!")
}
return nb.w
}
func (nb *plainList__Builder) Reset() {
*nb = plainList__Builder{}
nb.w = &plainList{}
}
// -- NodeAssembler -->
type plainList__Assembler struct {
w *plainList
va plainList__ValueAssembler
state laState
}
type plainList__ValueAssembler struct {
la *plainList__Assembler
}
// laState is an enum of the state machine for a list assembler.
// (this might be something to export reusably, but it's also very much an impl detail that need not be seen, so, dubious.)
// it's similar to maState for maps, but has fewer states because we never have keys to assemble.
type laState uint8
const (
laState_initial laState = iota // also the 'expect value or finish' state
laState_midValue // waiting for a 'finished' state in the ValueAssembler.
laState_finished // 'w' will also be nil, but this is a politer statement
)
func (plainList__Assembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return mixins.ListAssembler{TypeName: "list"}.BeginMap(0)
}
func (na *plainList__Assembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
if sizeHint < 0 {
sizeHint = 0
}
// Allocate storage space.
na.w.x = make([]datamodel.Node, 0, sizeHint)
// That's it; return self as the ListAssembler. We already have all the right methods on this structure.
return na, nil
}
func (plainList__Assembler) AssignNull() error {
return mixins.ListAssembler{TypeName: "list"}.AssignNull()
}
func (plainList__Assembler) AssignBool(bool) error {
return mixins.ListAssembler{TypeName: "list"}.AssignBool(false)
}
func (plainList__Assembler) AssignInt(int64) error {
return mixins.ListAssembler{TypeName: "list"}.AssignInt(0)
}
func (plainList__Assembler) AssignFloat(float64) error {
return mixins.ListAssembler{TypeName: "list"}.AssignFloat(0)
}
func (plainList__Assembler) AssignString(string) error {
return mixins.ListAssembler{TypeName: "list"}.AssignString("")
}
func (plainList__Assembler) AssignBytes([]byte) error {
return mixins.ListAssembler{TypeName: "list"}.AssignBytes(nil)
}
func (plainList__Assembler) AssignLink(datamodel.Link) error {
return mixins.ListAssembler{TypeName: "list"}.AssignLink(nil)
}
func (na *plainList__Assembler) AssignNode(v datamodel.Node) error {
// Sanity check, then update, assembler state.
// Update of state to 'finished' comes later; where exactly depends on if shortcuts apply.
if na.state != laState_initial {
panic("misuse")
}
// Copy the content.
if v2, ok := v.(*plainList); ok { // if our own type: shortcut.
// Copy the structure by value.
// This means we'll have pointers into the same internal maps and slices;
// this is okay, because the Node type promises it's immutable, and we are going to instantly finish ourselves to also maintain that.
// FIXME: the shortcut behaves differently than the long way: it discards any existing progress. Doesn't violate immut, but is odd.
*na.w = *v2
na.state = laState_finished
return nil
}
// If the above shortcut didn't work, resort to a generic copy.
// We call AssignNode for all the child values, giving them a chance to hit shortcuts even if we didn't.
if v.Kind() != datamodel.Kind_List {
return datamodel.ErrWrongKind{TypeName: "list", MethodName: "AssignNode", AppropriateKind: datamodel.KindSet_JustList, ActualKind: v.Kind()}
}
itr := v.ListIterator()
for !itr.Done() {
_, v, err := itr.Next()
if err != nil {
return err
}
if err := na.AssembleValue().AssignNode(v); err != nil {
return err
}
}
return na.Finish()
}
func (plainList__Assembler) Prototype() datamodel.NodePrototype {
return Prototype.List
}
// -- ListAssembler -->
// AssembleValue is part of conforming to ListAssembler, which we do on
// plainList__Assembler so that BeginList can just return a retyped pointer rather than new object.
func (la *plainList__Assembler) AssembleValue() datamodel.NodeAssembler {
// Sanity check, then update, assembler state.
if la.state != laState_initial {
panic("misuse")
}
la.state = laState_midValue
// Make value assembler valid by giving it pointer back to whole 'la'; yield it.
la.va.la = la
return &la.va
}
// Finish is part of conforming to ListAssembler, which we do on
// plainList__Assembler so that BeginList can just return a retyped pointer rather than new object.
func (la *plainList__Assembler) Finish() error {
// Sanity check, then update, assembler state.
if la.state != laState_initial {
panic("misuse")
}
la.state = laState_finished
// validators could run and report errors promptly, if this type had any.
return nil
}
func (plainList__Assembler) ValuePrototype(_ int64) datamodel.NodePrototype {
return Prototype.Any
}
// -- ListAssembler.ValueAssembler -->
func (lva *plainList__ValueAssembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
ma := plainList__ValueAssemblerMap{}
ma.ca.w = &plainMap{}
ma.p = lva.la
_, err := ma.ca.BeginMap(sizeHint)
return &ma, err
}
func (lva *plainList__ValueAssembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
la := plainList__ValueAssemblerList{}
la.ca.w = &plainList{}
la.p = lva.la
_, err := la.ca.BeginList(sizeHint)
return &la, err
}
func (lva *plainList__ValueAssembler) AssignNull() error {
return lva.AssignNode(datamodel.Null)
}
func (lva *plainList__ValueAssembler) AssignBool(v bool) error {
vb := plainBool(v)
return lva.AssignNode(&vb)
}
func (lva *plainList__ValueAssembler) AssignInt(v int64) error {
vb := plainInt(v)
return lva.AssignNode(&vb)
}
func (lva *plainList__ValueAssembler) AssignFloat(v float64) error {
vb := plainFloat(v)
return lva.AssignNode(&vb)
}
func (lva *plainList__ValueAssembler) AssignString(v string) error {
vb := plainString(v)
return lva.AssignNode(&vb)
}
func (lva *plainList__ValueAssembler) AssignBytes(v []byte) error {
vb := plainBytes(v)
return lva.AssignNode(&vb)
}
func (lva *plainList__ValueAssembler) AssignLink(v datamodel.Link) error {
vb := plainLink{v}
return lva.AssignNode(&vb)
}
func (lva *plainList__ValueAssembler) AssignNode(v datamodel.Node) error {
lva.la.w.x = append(lva.la.w.x, v)
lva.la.state = laState_initial
lva.la = nil // invalidate self to prevent further incorrect use.
return nil
}
func (plainList__ValueAssembler) Prototype() datamodel.NodePrototype {
return Prototype.Any
}
type plainList__ValueAssemblerMap struct {
ca plainMap__Assembler
p *plainList__Assembler // pointer back to parent, for final insert and state bump
}
// we briefly state only the methods we need to delegate here.
// just embedding plainMap__Assembler also behaves correctly,
// but causes a lot of unnecessary autogenerated functions in the final binary.
func (ma *plainList__ValueAssemblerMap) AssembleEntry(k string) (datamodel.NodeAssembler, error) {
return ma.ca.AssembleEntry(k)
}
func (ma *plainList__ValueAssemblerMap) AssembleKey() datamodel.NodeAssembler {
return ma.ca.AssembleKey()
}
func (ma *plainList__ValueAssemblerMap) AssembleValue() datamodel.NodeAssembler {
return ma.ca.AssembleValue()
}
func (plainList__ValueAssemblerMap) KeyPrototype() datamodel.NodePrototype {
return Prototype__String{}
}
func (plainList__ValueAssemblerMap) ValuePrototype(_ string) datamodel.NodePrototype {
return Prototype.Any
}
func (ma *plainList__ValueAssemblerMap) Finish() error {
if err := ma.ca.Finish(); err != nil {
return err
}
w := ma.ca.w
ma.ca.w = nil
return ma.p.va.AssignNode(w)
}
type plainList__ValueAssemblerList struct {
ca plainList__Assembler
p *plainList__Assembler // pointer back to parent, for final insert and state bump
}
// we briefly state only the methods we need to delegate here.
// just embedding plainList__Assembler also behaves correctly,
// but causes a lot of unnecessary autogenerated functions in the final binary.
func (la *plainList__ValueAssemblerList) AssembleValue() datamodel.NodeAssembler {
return la.ca.AssembleValue()
}
func (plainList__ValueAssemblerList) ValuePrototype(_ int64) datamodel.NodePrototype {
return Prototype.Any
}
func (la *plainList__ValueAssemblerList) Finish() error {
if err := la.ca.Finish(); err != nil {
return err
}
w := la.ca.w
la.ca.w = nil
return la.p.va.AssignNode(w)
}

View File

@@ -0,0 +1,474 @@
package basicnode
import (
"fmt"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/mixins"
)
var (
_ datamodel.Node = &plainMap{}
_ datamodel.NodePrototype = Prototype__Map{}
_ datamodel.NodeBuilder = &plainMap__Builder{}
_ datamodel.NodeAssembler = &plainMap__Assembler{}
)
// plainMap is a concrete type that provides a map-kind datamodel.Node.
// It can contain any kind of value.
// plainMap is also embedded in the 'any' struct and usable from there.
type plainMap struct {
m map[string]datamodel.Node // string key -- even if a runtime schema wrapper is using us for storage, we must have a comparable type here, and string is all we know.
t []plainMap__Entry // table for fast iteration, order keeping, and yielding pointers to enable alloc/conv amortization.
}
type plainMap__Entry struct {
k plainString // address of this used when we return keys as nodes, such as in iterators. Need in one place to amortize shifts to heap when ptr'ing for iface.
v datamodel.Node // identical to map values. keeping them here simplifies iteration. (in codegen'd maps, this position is also part of amortization, but in this implementation, that's less useful.)
// note on alternate implementations: 'v' could also use the 'any' type, and thus amortize value allocations. the memory size trade would be large however, so we don't, here.
}
// -- Node interface methods -->
func (plainMap) Kind() datamodel.Kind {
return datamodel.Kind_Map
}
func (n *plainMap) LookupByString(key string) (datamodel.Node, error) {
v, exists := n.m[key]
if !exists {
return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfString(key)}
}
return v, nil
}
func (n *plainMap) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
ks, err := key.AsString()
if err != nil {
return nil, err
}
return n.LookupByString(ks)
}
func (plainMap) LookupByIndex(idx int64) (datamodel.Node, error) {
return mixins.Map{TypeName: "map"}.LookupByIndex(0)
}
func (n *plainMap) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
return n.LookupByString(seg.String())
}
func (n *plainMap) MapIterator() datamodel.MapIterator {
return &plainMap_MapIterator{n, 0}
}
func (plainMap) ListIterator() datamodel.ListIterator {
return nil
}
func (n *plainMap) Length() int64 {
return int64(len(n.t))
}
func (plainMap) IsAbsent() bool {
return false
}
func (plainMap) IsNull() bool {
return false
}
func (plainMap) AsBool() (bool, error) {
return mixins.Map{TypeName: "map"}.AsBool()
}
func (plainMap) AsInt() (int64, error) {
return mixins.Map{TypeName: "map"}.AsInt()
}
func (plainMap) AsFloat() (float64, error) {
return mixins.Map{TypeName: "map"}.AsFloat()
}
func (plainMap) AsString() (string, error) {
return mixins.Map{TypeName: "map"}.AsString()
}
func (plainMap) AsBytes() ([]byte, error) {
return mixins.Map{TypeName: "map"}.AsBytes()
}
func (plainMap) AsLink() (datamodel.Link, error) {
return mixins.Map{TypeName: "map"}.AsLink()
}
func (plainMap) Prototype() datamodel.NodePrototype {
return Prototype.Map
}
type plainMap_MapIterator struct {
n *plainMap
idx int
}
func (itr *plainMap_MapIterator) Next() (k datamodel.Node, v datamodel.Node, _ error) {
if itr.Done() {
return nil, nil, datamodel.ErrIteratorOverread{}
}
k = &itr.n.t[itr.idx].k
v = itr.n.t[itr.idx].v
itr.idx++
return
}
func (itr *plainMap_MapIterator) Done() bool {
return itr.idx >= len(itr.n.t)
}
// -- NodePrototype -->
type Prototype__Map struct{}
func (Prototype__Map) NewBuilder() datamodel.NodeBuilder {
return &plainMap__Builder{plainMap__Assembler{w: &plainMap{}}}
}
// -- NodeBuilder -->
type plainMap__Builder struct {
plainMap__Assembler
}
func (nb *plainMap__Builder) Build() datamodel.Node {
if nb.state != maState_finished {
panic("invalid state: assembler must be 'finished' before Build can be called!")
}
return nb.w
}
func (nb *plainMap__Builder) Reset() {
*nb = plainMap__Builder{}
nb.w = &plainMap{}
}
// -- NodeAssembler -->
type plainMap__Assembler struct {
w *plainMap
ka plainMap__KeyAssembler
va plainMap__ValueAssembler
state maState
}
type plainMap__KeyAssembler struct {
ma *plainMap__Assembler
}
type plainMap__ValueAssembler struct {
ma *plainMap__Assembler
}
// maState is an enum of the state machine for a map assembler.
// (this might be something to export reusably, but it's also very much an impl detail that need not be seen, so, dubious.)
type maState uint8
const (
maState_initial maState = iota // also the 'expect key or finish' state
maState_midKey // waiting for a 'finished' state in the KeyAssembler.
maState_expectValue // 'AssembleValue' is the only valid next step
maState_midValue // waiting for a 'finished' state in the ValueAssembler.
maState_finished // 'w' will also be nil, but this is a politer statement
)
func (na *plainMap__Assembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
if sizeHint < 0 {
sizeHint = 0
}
// Allocate storage space.
na.w.t = make([]plainMap__Entry, 0, sizeHint)
na.w.m = make(map[string]datamodel.Node, sizeHint)
// That's it; return self as the MapAssembler. We already have all the right methods on this structure.
return na, nil
}
func (plainMap__Assembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return mixins.MapAssembler{TypeName: "map"}.BeginList(0)
}
func (plainMap__Assembler) AssignNull() error {
return mixins.MapAssembler{TypeName: "map"}.AssignNull()
}
func (plainMap__Assembler) AssignBool(bool) error {
return mixins.MapAssembler{TypeName: "map"}.AssignBool(false)
}
func (plainMap__Assembler) AssignInt(int64) error {
return mixins.MapAssembler{TypeName: "map"}.AssignInt(0)
}
func (plainMap__Assembler) AssignFloat(float64) error {
return mixins.MapAssembler{TypeName: "map"}.AssignFloat(0)
}
func (plainMap__Assembler) AssignString(string) error {
return mixins.MapAssembler{TypeName: "map"}.AssignString("")
}
func (plainMap__Assembler) AssignBytes([]byte) error {
return mixins.MapAssembler{TypeName: "map"}.AssignBytes(nil)
}
func (plainMap__Assembler) AssignLink(datamodel.Link) error {
return mixins.MapAssembler{TypeName: "map"}.AssignLink(nil)
}
func (na *plainMap__Assembler) AssignNode(v datamodel.Node) error {
// Sanity check assembler state.
// Update of state to 'finished' comes later; where exactly depends on if shortcuts apply.
if na.state != maState_initial {
panic("misuse")
}
// Copy the content.
if v2, ok := v.(*plainMap); ok { // if our own type: shortcut.
// Copy the structure by value.
// This means we'll have pointers into the same internal maps and slices;
// this is okay, because the Node type promises it's immutable, and we are going to instantly finish ourselves to also maintain that.
// FIXME: the shortcut behaves differently than the long way: it discards any existing progress. Doesn't violate immut, but is odd.
*na.w = *v2
na.state = maState_finished
return nil
}
// If the above shortcut didn't work, resort to a generic copy.
// We call AssignNode for all the child values, giving them a chance to hit shortcuts even if we didn't.
if v.Kind() != datamodel.Kind_Map {
return datamodel.ErrWrongKind{TypeName: "map", MethodName: "AssignNode", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: v.Kind()}
}
itr := v.MapIterator()
for !itr.Done() {
k, v, err := itr.Next()
if err != nil {
return err
}
if err := na.AssembleKey().AssignNode(k); err != nil {
return err
}
if err := na.AssembleValue().AssignNode(v); err != nil {
return err
}
}
return na.Finish()
}
func (plainMap__Assembler) Prototype() datamodel.NodePrototype {
return Prototype.Map
}
// -- MapAssembler -->
// AssembleEntry is part of conforming to MapAssembler, which we do on
// plainMap__Assembler so that BeginMap can just return a retyped pointer rather than new object.
func (ma *plainMap__Assembler) AssembleEntry(k string) (datamodel.NodeAssembler, error) {
// Sanity check assembler state.
// Update of state comes after possible key rejection.
if ma.state != maState_initial {
panic("misuse")
}
// Check for dup keys; error if so.
_, exists := ma.w.m[k]
if exists {
return nil, datamodel.ErrRepeatedMapKey{Key: plainString(k)}
}
ma.state = maState_midValue
ma.w.t = append(ma.w.t, plainMap__Entry{k: plainString(k)})
// Make value assembler valid by giving it pointer back to whole 'ma'; yield it.
ma.va.ma = ma
return &ma.va, nil
}
// AssembleKey is part of conforming to MapAssembler, which we do on
// plainMap__Assembler so that BeginMap can just return a retyped pointer rather than new object.
func (ma *plainMap__Assembler) AssembleKey() datamodel.NodeAssembler {
// Sanity check, then update, assembler state.
if ma.state != maState_initial {
panic("misuse")
}
ma.state = maState_midKey
// Make key assembler valid by giving it pointer back to whole 'ma'; yield it.
ma.ka.ma = ma
return &ma.ka
}
// AssembleValue is part of conforming to MapAssembler, which we do on
// plainMap__Assembler so that BeginMap can just return a retyped pointer rather than new object.
func (ma *plainMap__Assembler) AssembleValue() datamodel.NodeAssembler {
// Sanity check, then update, assembler state.
if ma.state != maState_expectValue {
panic("misuse")
}
ma.state = maState_midValue
// Make value assembler valid by giving it pointer back to whole 'ma'; yield it.
ma.va.ma = ma
return &ma.va
}
// Finish is part of conforming to MapAssembler, which we do on
// plainMap__Assembler so that BeginMap can just return a retyped pointer rather than new object.
func (ma *plainMap__Assembler) Finish() error {
// Sanity check, then update, assembler state.
if ma.state != maState_initial {
panic("misuse")
}
ma.state = maState_finished
// validators could run and report errors promptly, if this type had any.
return nil
}
func (plainMap__Assembler) KeyPrototype() datamodel.NodePrototype {
return Prototype__String{}
}
func (plainMap__Assembler) ValuePrototype(_ string) datamodel.NodePrototype {
return Prototype.Any
}
// -- MapAssembler.KeyAssembler -->
func (plainMap__KeyAssembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return mixins.StringAssembler{TypeName: "string"}.BeginMap(0)
}
func (plainMap__KeyAssembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return mixins.StringAssembler{TypeName: "string"}.BeginList(0)
}
func (plainMap__KeyAssembler) AssignNull() error {
return mixins.StringAssembler{TypeName: "string"}.AssignNull()
}
func (plainMap__KeyAssembler) AssignBool(bool) error {
return mixins.StringAssembler{TypeName: "string"}.AssignBool(false)
}
func (plainMap__KeyAssembler) AssignInt(int64) error {
return mixins.StringAssembler{TypeName: "string"}.AssignInt(0)
}
func (plainMap__KeyAssembler) AssignFloat(float64) error {
return mixins.StringAssembler{TypeName: "string"}.AssignFloat(0)
}
func (mka *plainMap__KeyAssembler) AssignString(v string) error {
// Check for dup keys; error if so.
// (And, backtrack state to accepting keys again so we don't get eternally wedged here.)
_, exists := mka.ma.w.m[v]
if exists {
mka.ma.state = maState_initial
mka.ma = nil // invalidate self to prevent further incorrect use.
return datamodel.ErrRepeatedMapKey{Key: plainString(v)}
}
// Assign the key into the end of the entry table;
// we'll be doing map insertions after we get the value in hand.
// (There's no need to delegate to another assembler for the key type,
// because we're just at Data Model level here, which only regards plain strings.)
mka.ma.w.t = append(mka.ma.w.t, plainMap__Entry{})
mka.ma.w.t[len(mka.ma.w.t)-1].k = plainString(v)
// Update parent assembler state: clear to proceed.
mka.ma.state = maState_expectValue
mka.ma = nil // invalidate self to prevent further incorrect use.
return nil
}
func (plainMap__KeyAssembler) AssignBytes([]byte) error {
return mixins.StringAssembler{TypeName: "string"}.AssignBytes(nil)
}
func (plainMap__KeyAssembler) AssignLink(datamodel.Link) error {
return mixins.StringAssembler{TypeName: "string"}.AssignLink(nil)
}
func (mka *plainMap__KeyAssembler) AssignNode(v datamodel.Node) error {
vs, err := v.AsString()
if err != nil {
return fmt.Errorf("cannot assign non-string node into map key assembler") // FIXME:errors: this doesn't quite fit in ErrWrongKind cleanly; new error type?
}
return mka.AssignString(vs)
}
func (plainMap__KeyAssembler) Prototype() datamodel.NodePrototype {
return Prototype__String{}
}
// -- MapAssembler.ValueAssembler -->
func (mva *plainMap__ValueAssembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
ma := plainMap__ValueAssemblerMap{}
ma.ca.w = &plainMap{}
ma.p = mva.ma
_, err := ma.ca.BeginMap(sizeHint)
return &ma, err
}
func (mva *plainMap__ValueAssembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
la := plainMap__ValueAssemblerList{}
la.ca.w = &plainList{}
la.p = mva.ma
_, err := la.ca.BeginList(sizeHint)
return &la, err
}
func (mva *plainMap__ValueAssembler) AssignNull() error {
return mva.AssignNode(datamodel.Null)
}
func (mva *plainMap__ValueAssembler) AssignBool(v bool) error {
vb := plainBool(v)
return mva.AssignNode(&vb)
}
func (mva *plainMap__ValueAssembler) AssignInt(v int64) error {
vb := plainInt(v)
return mva.AssignNode(&vb)
}
func (mva *plainMap__ValueAssembler) AssignFloat(v float64) error {
vb := plainFloat(v)
return mva.AssignNode(&vb)
}
func (mva *plainMap__ValueAssembler) AssignString(v string) error {
vb := plainString(v)
return mva.AssignNode(&vb)
}
func (mva *plainMap__ValueAssembler) AssignBytes(v []byte) error {
vb := plainBytes(v)
return mva.AssignNode(&vb)
}
func (mva *plainMap__ValueAssembler) AssignLink(v datamodel.Link) error {
vb := plainLink{v}
return mva.AssignNode(&vb)
}
func (mva *plainMap__ValueAssembler) AssignNode(v datamodel.Node) error {
l := len(mva.ma.w.t) - 1
mva.ma.w.t[l].v = v
mva.ma.w.m[string(mva.ma.w.t[l].k)] = v
mva.ma.state = maState_initial
mva.ma = nil // invalidate self to prevent further incorrect use.
return nil
}
func (plainMap__ValueAssembler) Prototype() datamodel.NodePrototype {
return Prototype.Any
}
type plainMap__ValueAssemblerMap struct {
ca plainMap__Assembler
p *plainMap__Assembler // pointer back to parent, for final insert and state bump
}
// we briefly state only the methods we need to delegate here.
// just embedding plainMap__Assembler also behaves correctly,
// but causes a lot of unnecessary autogenerated functions in the final binary.
func (ma *plainMap__ValueAssemblerMap) AssembleEntry(k string) (datamodel.NodeAssembler, error) {
return ma.ca.AssembleEntry(k)
}
func (ma *plainMap__ValueAssemblerMap) AssembleKey() datamodel.NodeAssembler {
return ma.ca.AssembleKey()
}
func (ma *plainMap__ValueAssemblerMap) AssembleValue() datamodel.NodeAssembler {
return ma.ca.AssembleValue()
}
func (plainMap__ValueAssemblerMap) KeyPrototype() datamodel.NodePrototype {
return Prototype__String{}
}
func (plainMap__ValueAssemblerMap) ValuePrototype(_ string) datamodel.NodePrototype {
return Prototype.Any
}
func (ma *plainMap__ValueAssemblerMap) Finish() error {
if err := ma.ca.Finish(); err != nil {
return err
}
w := ma.ca.w
ma.ca.w = nil
return ma.p.va.AssignNode(w)
}
type plainMap__ValueAssemblerList struct {
ca plainList__Assembler
p *plainMap__Assembler // pointer back to parent, for final insert and state bump
}
// we briefly state only the methods we need to delegate here.
// just embedding plainList__Assembler also behaves correctly,
// but causes a lot of unnecessary autogenerated functions in the final binary.
func (la *plainMap__ValueAssemblerList) AssembleValue() datamodel.NodeAssembler {
return la.ca.AssembleValue()
}
func (plainMap__ValueAssemblerList) ValuePrototype(_ int64) datamodel.NodePrototype {
return Prototype.Any
}
func (la *plainMap__ValueAssemblerList) Finish() error {
if err := la.ca.Finish(); err != nil {
return err
}
w := la.ca.w
la.ca.w = nil
return la.p.va.AssignNode(w)
}

View File

@@ -0,0 +1,26 @@
package basicnode
// Prototype embeds a NodePrototype for every kind of Node implementation in this package.
// You can use it like this:
//
// basicnode.Prototype.Map.NewBuilder().BeginMap() //...
//
// and:
//
// basicnode.Prototype.String.NewBuilder().AssignString("x") // ...
//
// Most of the prototypes are for one particular Kind of node (e.g. string, int, etc);
// you can use the "Any" style if you want a builder that can accept any kind of data.
var Prototype prototype
type prototype struct {
Any Prototype__Any
Map Prototype__Map
List Prototype__List
Bool Prototype__Bool
Int Prototype__Int
Float Prototype__Float
String Prototype__String
Bytes Prototype__Bytes
Link Prototype__Link
}

View File

@@ -0,0 +1,149 @@
package basicnode
import (
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/mixins"
)
var (
_ datamodel.Node = plainString("")
_ datamodel.NodePrototype = Prototype__String{}
_ datamodel.NodeBuilder = &plainString__Builder{}
_ datamodel.NodeAssembler = &plainString__Assembler{}
)
func NewString(value string) datamodel.Node {
v := plainString(value)
return &v
}
// plainString is a simple boxed string that complies with datamodel.Node.
// It's useful for many things, such as boxing map keys.
//
// The implementation is a simple typedef of a string;
// handling it as a Node incurs 'runtime.convTstring',
// which is about the best we can do.
type plainString string
// -- Node interface methods -->
func (plainString) Kind() datamodel.Kind {
return datamodel.Kind_String
}
func (plainString) LookupByString(string) (datamodel.Node, error) {
return mixins.String{TypeName: "string"}.LookupByString("")
}
func (plainString) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return mixins.String{TypeName: "string"}.LookupByNode(nil)
}
func (plainString) LookupByIndex(idx int64) (datamodel.Node, error) {
return mixins.String{TypeName: "string"}.LookupByIndex(0)
}
func (plainString) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
return mixins.String{TypeName: "string"}.LookupBySegment(seg)
}
func (plainString) MapIterator() datamodel.MapIterator {
return nil
}
func (plainString) ListIterator() datamodel.ListIterator {
return nil
}
func (plainString) Length() int64 {
return -1
}
func (plainString) IsAbsent() bool {
return false
}
func (plainString) IsNull() bool {
return false
}
func (plainString) AsBool() (bool, error) {
return mixins.String{TypeName: "string"}.AsBool()
}
func (plainString) AsInt() (int64, error) {
return mixins.String{TypeName: "string"}.AsInt()
}
func (plainString) AsFloat() (float64, error) {
return mixins.String{TypeName: "string"}.AsFloat()
}
func (x plainString) AsString() (string, error) {
return string(x), nil
}
func (plainString) AsBytes() ([]byte, error) {
return mixins.String{TypeName: "string"}.AsBytes()
}
func (plainString) AsLink() (datamodel.Link, error) {
return mixins.String{TypeName: "string"}.AsLink()
}
func (plainString) Prototype() datamodel.NodePrototype {
return Prototype__String{}
}
// -- NodePrototype -->
type Prototype__String struct{}
func (Prototype__String) NewBuilder() datamodel.NodeBuilder {
var w plainString
return &plainString__Builder{plainString__Assembler{w: &w}}
}
// -- NodeBuilder -->
type plainString__Builder struct {
plainString__Assembler
}
func (nb *plainString__Builder) Build() datamodel.Node {
return nb.w
}
func (nb *plainString__Builder) Reset() {
var w plainString
*nb = plainString__Builder{plainString__Assembler{w: &w}}
}
// -- NodeAssembler -->
type plainString__Assembler struct {
w *plainString
}
func (plainString__Assembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return mixins.StringAssembler{TypeName: "string"}.BeginMap(0)
}
func (plainString__Assembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return mixins.StringAssembler{TypeName: "string"}.BeginList(0)
}
func (plainString__Assembler) AssignNull() error {
return mixins.StringAssembler{TypeName: "string"}.AssignNull()
}
func (plainString__Assembler) AssignBool(bool) error {
return mixins.StringAssembler{TypeName: "string"}.AssignBool(false)
}
func (plainString__Assembler) AssignInt(int64) error {
return mixins.StringAssembler{TypeName: "string"}.AssignInt(0)
}
func (plainString__Assembler) AssignFloat(float64) error {
return mixins.StringAssembler{TypeName: "string"}.AssignFloat(0)
}
func (na *plainString__Assembler) AssignString(v string) error {
*na.w = plainString(v)
return nil
}
func (plainString__Assembler) AssignBytes([]byte) error {
return mixins.StringAssembler{TypeName: "string"}.AssignBytes(nil)
}
func (plainString__Assembler) AssignLink(datamodel.Link) error {
return mixins.StringAssembler{TypeName: "string"}.AssignLink(nil)
}
func (na *plainString__Assembler) AssignNode(v datamodel.Node) error {
if v2, err := v.AsString(); err != nil {
return err
} else {
*na.w = plainString(v2)
return nil
}
}
func (plainString__Assembler) Prototype() datamodel.NodePrototype {
return Prototype__String{}
}

View File

@@ -0,0 +1,335 @@
// Package bindnode provides a datamodel.Node implementation via Go reflection.
//
// This package is EXPERIMENTAL; its behavior and API might change as it's still
// in development.
package bindnode
import (
"reflect"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/schema"
)
// Prototype implements a schema.TypedPrototype given a Go pointer type and an
// IPLD schema type. Note that the result is also a datamodel.NodePrototype.
//
// If both the Go type and schema type are supplied, it is assumed that they are
// compatible with one another.
//
// If either the Go type or schema type are nil, we infer the missing type from
// the other provided type. For example, we can infer an unnamed Go struct type
// for a schema struct type, and we can infer a schema Int type for a Go int64
// type. The inferring logic is still a work in progress and subject to change.
// At this time, inferring IPLD Unions and Enums from Go types is not supported.
//
// When supplying a non-nil ptrType, Prototype only obtains the Go pointer type
// from it, so its underlying value will typically be nil. For example:
//
// proto := bindnode.Prototype((*goType)(nil), schemaType)
func Prototype(ptrType interface{}, schemaType schema.Type, options ...Option) schema.TypedPrototype {
if ptrType == nil && schemaType == nil {
panic("bindnode: either ptrType or schemaType must not be nil")
}
cfg := applyOptions(options...)
// TODO: if both are supplied, verify that they are compatible
var goType reflect.Type
if ptrType == nil {
goType = inferGoType(schemaType, make(map[schema.TypeName]inferredStatus), 0)
} else {
goPtrType := reflect.TypeOf(ptrType)
if goPtrType.Kind() != reflect.Ptr {
panic("bindnode: ptrType must be a pointer")
}
goType = goPtrType.Elem()
if goType.Kind() == reflect.Ptr {
panic("bindnode: ptrType must not be a pointer to a pointer")
}
if schemaType == nil {
schemaType = inferSchema(goType, 0)
} else {
verifyCompatibility(cfg, make(map[seenEntry]bool), goType, schemaType)
}
}
return &_prototype{cfg: cfg, schemaType: schemaType, goType: goType}
}
type converter struct {
kind schema.TypeKind
customFromBool func(bool) (interface{}, error)
customToBool func(interface{}) (bool, error)
customFromInt func(int64) (interface{}, error)
customToInt func(interface{}) (int64, error)
customFromFloat func(float64) (interface{}, error)
customToFloat func(interface{}) (float64, error)
customFromString func(string) (interface{}, error)
customToString func(interface{}) (string, error)
customFromBytes func([]byte) (interface{}, error)
customToBytes func(interface{}) ([]byte, error)
customFromLink func(cid.Cid) (interface{}, error)
customToLink func(interface{}) (cid.Cid, error)
customFromAny func(datamodel.Node) (interface{}, error)
customToAny func(interface{}) (datamodel.Node, error)
}
type config map[reflect.Type]*converter
// this mainly exists to short-circuit the nonPtrType() call; the `Type()` variant
// exists for completeness
func (c config) converterFor(val reflect.Value) *converter {
if len(c) == 0 {
return nil
}
return c[nonPtrType(val)]
}
func (c config) converterForType(typ reflect.Type) *converter {
if len(c) == 0 {
return nil
}
return c[typ]
}
// Option is able to apply custom options to the bindnode API
type Option func(config)
// TypedBoolConverter adds custom converter functions for a particular
// type as identified by a pointer in the first argument.
// The fromFunc is of the form: func(bool) (interface{}, error)
// and toFunc is of the form: func(interface{}) (bool, error)
// where interface{} is a pointer form of the type we are converting.
//
// TypedBoolConverter is an EXPERIMENTAL API and may be removed or
// changed in a future release.
func TypedBoolConverter(ptrVal interface{}, from func(bool) (interface{}, error), to func(interface{}) (bool, error)) Option {
customType := nonPtrType(reflect.ValueOf(ptrVal))
converter := &converter{
kind: schema.TypeKind_Bool,
customFromBool: from,
customToBool: to,
}
return func(cfg config) {
cfg[customType] = converter
}
}
// TypedIntConverter adds custom converter functions for a particular
// type as identified by a pointer in the first argument.
// The fromFunc is of the form: func(int64) (interface{}, error)
// and toFunc is of the form: func(interface{}) (int64, error)
// where interface{} is a pointer form of the type we are converting.
//
// TypedIntConverter is an EXPERIMENTAL API and may be removed or
// changed in a future release.
func TypedIntConverter(ptrVal interface{}, from func(int64) (interface{}, error), to func(interface{}) (int64, error)) Option {
customType := nonPtrType(reflect.ValueOf(ptrVal))
converter := &converter{
kind: schema.TypeKind_Int,
customFromInt: from,
customToInt: to,
}
return func(cfg config) {
cfg[customType] = converter
}
}
// TypedFloatConverter adds custom converter functions for a particular
// type as identified by a pointer in the first argument.
// The fromFunc is of the form: func(float64) (interface{}, error)
// and toFunc is of the form: func(interface{}) (float64, error)
// where interface{} is a pointer form of the type we are converting.
//
// TypedFloatConverter is an EXPERIMENTAL API and may be removed or
// changed in a future release.
func TypedFloatConverter(ptrVal interface{}, from func(float64) (interface{}, error), to func(interface{}) (float64, error)) Option {
customType := nonPtrType(reflect.ValueOf(ptrVal))
converter := &converter{
kind: schema.TypeKind_Float,
customFromFloat: from,
customToFloat: to,
}
return func(cfg config) {
cfg[customType] = converter
}
}
// TypedStringConverter adds custom converter functions for a particular
// type as identified by a pointer in the first argument.
// The fromFunc is of the form: func(string) (interface{}, error)
// and toFunc is of the form: func(interface{}) (string, error)
// where interface{} is a pointer form of the type we are converting.
//
// TypedStringConverter is an EXPERIMENTAL API and may be removed or
// changed in a future release.
func TypedStringConverter(ptrVal interface{}, from func(string) (interface{}, error), to func(interface{}) (string, error)) Option {
customType := nonPtrType(reflect.ValueOf(ptrVal))
converter := &converter{
kind: schema.TypeKind_String,
customFromString: from,
customToString: to,
}
return func(cfg config) {
cfg[customType] = converter
}
}
// TypedBytesConverter adds custom converter functions for a particular
// type as identified by a pointer in the first argument.
// The fromFunc is of the form: func([]byte) (interface{}, error)
// and toFunc is of the form: func(interface{}) ([]byte, error)
// where interface{} is a pointer form of the type we are converting.
//
// TypedBytesConverter is an EXPERIMENTAL API and may be removed or
// changed in a future release.
func TypedBytesConverter(ptrVal interface{}, from func([]byte) (interface{}, error), to func(interface{}) ([]byte, error)) Option {
customType := nonPtrType(reflect.ValueOf(ptrVal))
converter := &converter{
kind: schema.TypeKind_Bytes,
customFromBytes: from,
customToBytes: to,
}
return func(cfg config) {
cfg[customType] = converter
}
}
// TypedLinkConverter adds custom converter functions for a particular
// type as identified by a pointer in the first argument.
// The fromFunc is of the form: func([]byte) (interface{}, error)
// and toFunc is of the form: func(interface{}) ([]byte, error)
// where interface{} is a pointer form of the type we are converting.
//
// Beware that this API is only compatible with cidlink.Link types in the data
// model and may result in errors if attempting to convert from other
// datamodel.Link types.
//
// TypedLinkConverter is an EXPERIMENTAL API and may be removed or
// changed in a future release.
func TypedLinkConverter(ptrVal interface{}, from func(cid.Cid) (interface{}, error), to func(interface{}) (cid.Cid, error)) Option {
customType := nonPtrType(reflect.ValueOf(ptrVal))
converter := &converter{
kind: schema.TypeKind_Link,
customFromLink: from,
customToLink: to,
}
return func(cfg config) {
cfg[customType] = converter
}
}
// TypedAnyConverter adds custom converter functions for a particular
// type as identified by a pointer in the first argument.
// The fromFunc is of the form: func(datamodel.Node) (interface{}, error)
// and toFunc is of the form: func(interface{}) (datamodel.Node, error)
// where interface{} is a pointer form of the type we are converting.
//
// This method should be able to deal with all forms of Any and return an error
// if the expected data forms don't match the expected.
//
// TypedAnyConverter is an EXPERIMENTAL API and may be removed or
// changed in a future release.
func TypedAnyConverter(ptrVal interface{}, from func(datamodel.Node) (interface{}, error), to func(interface{}) (datamodel.Node, error)) Option {
customType := nonPtrType(reflect.ValueOf(ptrVal))
converter := &converter{
kind: schema.TypeKind_Any,
customFromAny: from,
customToAny: to,
}
return func(cfg config) {
cfg[customType] = converter
}
}
func applyOptions(opt ...Option) config {
if len(opt) == 0 {
// no need to allocate, we access it via converterFor and converterForType
// which are safe for nil maps
return nil
}
cfg := make(map[reflect.Type]*converter)
for _, o := range opt {
o(cfg)
}
return cfg
}
// Wrap implements a schema.TypedNode given a non-nil pointer to a Go value and an
// IPLD schema type. Note that the result is also a datamodel.Node.
//
// Wrap is meant to be used when one already has a Go value with data.
// As such, ptrVal must not be nil.
//
// Similar to Prototype, if schemaType is non-nil it is assumed to be compatible
// with the Go type, and otherwise it's inferred from the Go type.
func Wrap(ptrVal interface{}, schemaType schema.Type, options ...Option) schema.TypedNode {
if ptrVal == nil {
panic("bindnode: ptrVal must not be nil")
}
goPtrVal := reflect.ValueOf(ptrVal)
if goPtrVal.Kind() != reflect.Ptr {
panic("bindnode: ptrVal must be a pointer")
}
if goPtrVal.IsNil() {
// Note that this can happen if ptrVal was a typed nil.
panic("bindnode: ptrVal must not be nil")
}
cfg := applyOptions(options...)
goVal := goPtrVal.Elem()
if goVal.Kind() == reflect.Ptr {
panic("bindnode: ptrVal must not be a pointer to a pointer")
}
if schemaType == nil {
schemaType = inferSchema(goVal.Type(), 0)
} else {
// TODO(rvagg): explore ways to make this skippable by caching in the schema.Type
// passed in to this function; e.g. if you call Prototype(), then you've gone through
// this already, then calling .Type() on that could return a bindnode version of
// schema.Type that has the config cached and can be assumed to have been checked or
// inferred.
verifyCompatibility(cfg, make(map[seenEntry]bool), goVal.Type(), schemaType)
}
return newNode(cfg, schemaType, goVal)
}
// TODO: consider making our own Node interface, like:
//
// type WrappedNode interface {
// datamodel.Node
// Unwrap() (ptrVal interface)
// }
//
// Pros: API is easier to understand, harder to mix up with other datamodel.Nodes.
// Cons: One usually only has a datamodel.Node, and type assertions can be weird.
// Unwrap takes a datamodel.Node implemented by Prototype or Wrap,
// and returns a pointer to the inner Go value.
//
// Unwrap returns nil if the node isn't implemented by this package.
func Unwrap(node datamodel.Node) (ptrVal interface{}) {
var val reflect.Value
switch node := node.(type) {
case *_node:
val = node.val
case *_nodeRepr:
val = node.val
default:
return nil
}
if val.Kind() == reflect.Ptr {
panic("bindnode: didn't expect val to be a pointer")
}
return val.Addr().Interface()
}

View File

@@ -0,0 +1,139 @@
package bindnode
import (
"bytes"
"fmt"
"go/format"
"io"
"strings"
"github.com/ipld/go-ipld-prime/schema"
)
// TODO(mvdan): deduplicate with inferGoType once reflect supports creating named types
func produceGoType(goTypes map[string]string, typ schema.Type) (name, src string) {
if typ, ok := typ.(interface{ IsAnonymous() bool }); ok {
if typ.IsAnonymous() {
panic("TODO: does this ever happen?")
}
}
name = string(typ.Name())
switch typ.(type) {
case *schema.TypeBool:
return goTypeBool.String(), ""
case *schema.TypeInt:
return goTypeInt.String(), ""
case *schema.TypeFloat:
return goTypeFloat.String(), ""
case *schema.TypeString:
return goTypeString.String(), ""
case *schema.TypeBytes:
return goTypeBytes.String(), ""
case *schema.TypeLink:
return goTypeLink.String(), "" // datamodel.Link
case *schema.TypeAny:
return goTypeNode.String(), "" // datamodel.Node
}
// Results are cached in goTypes.
if src := goTypes[name]; src != "" {
return name, src
}
src = produceGoTypeInner(goTypes, name, typ)
goTypes[name] = src
return name, src
}
func produceGoTypeInner(goTypes map[string]string, name string, typ schema.Type) (src string) {
// Avoid infinite cycles.
// produceGoType will fill in the final type later.
goTypes[name] = "WIP"
switch typ := typ.(type) {
case *schema.TypeEnum:
// TODO: also generate named constants for the members.
return goTypeString.String()
case *schema.TypeStruct:
var b strings.Builder
fmt.Fprintf(&b, "struct {\n")
fields := typ.Fields()
for _, field := range fields {
fmt.Fprintf(&b, "%s ", fieldNameFromSchema(field.Name()))
ftypGo, _ := produceGoType(goTypes, field.Type())
if field.IsNullable() {
fmt.Fprintf(&b, "*")
}
if field.IsOptional() {
fmt.Fprintf(&b, "*")
}
fmt.Fprintf(&b, "%s\n", ftypGo)
}
fmt.Fprintf(&b, "\n}")
return b.String()
case *schema.TypeMap:
ktyp, _ := produceGoType(goTypes, typ.KeyType())
vtyp, _ := produceGoType(goTypes, typ.ValueType())
if typ.ValueIsNullable() {
vtyp = "*" + vtyp
}
return fmt.Sprintf(`struct {
Keys []%s
Values map[%s]%s
}`, ktyp, ktyp, vtyp)
case *schema.TypeList:
etyp, _ := produceGoType(goTypes, typ.ValueType())
if typ.ValueIsNullable() {
etyp = "*" + etyp
}
return fmt.Sprintf("[]%s", etyp)
case *schema.TypeUnion:
var b strings.Builder
fmt.Fprintf(&b, "struct{\n")
members := typ.Members()
for _, ftyp := range members {
ftypGo, _ := produceGoType(goTypes, ftyp)
fmt.Fprintf(&b, "%s ", fieldNameFromSchema(string(ftyp.Name())))
fmt.Fprintf(&b, "*%s\n", ftypGo)
}
fmt.Fprintf(&b, "\n}")
return b.String()
}
panic(fmt.Sprintf("%T\n", typ))
}
// ProduceGoTypes infers Go types from an IPLD schema in ts
// and writes their Go source code type declarations to w.
// Note that just the types are written,
// without a package declaration nor any imports.
//
// This gives a good starting point when wanting to use bindnode with Go types,
// but users will generally want to own and modify the types afterward,
// so they can add documentation or tweak the types as needed.
func ProduceGoTypes(w io.Writer, ts *schema.TypeSystem) error {
goTypes := make(map[string]string)
var buf bytes.Buffer
for _, name := range ts.Names() {
schemaType := ts.TypeByName(string(name))
if name != schemaType.Name() {
panic(fmt.Sprintf("%s vs %s", name, schemaType.Name()))
}
_, src := produceGoType(goTypes, schemaType)
if src == "" {
continue // scalar type used directly
}
fmt.Fprintf(&buf, "type %s %s\n", name, src)
}
src, err := format.Source(buf.Bytes())
if err != nil {
return err
}
if _, err := w.Write(src); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,507 @@
package bindnode
import (
"fmt"
"go/token"
"reflect"
"strings"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime/datamodel"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipld/go-ipld-prime/schema"
)
var (
goTypeBool = reflect.TypeOf(false)
goTypeInt = reflect.TypeOf(int(0))
goTypeFloat = reflect.TypeOf(0.0)
goTypeString = reflect.TypeOf("")
goTypeBytes = reflect.TypeOf([]byte{})
goTypeLink = reflect.TypeOf((*datamodel.Link)(nil)).Elem()
goTypeNode = reflect.TypeOf((*datamodel.Node)(nil)).Elem()
goTypeCidLink = reflect.TypeOf((*cidlink.Link)(nil)).Elem()
goTypeCid = reflect.TypeOf((*cid.Cid)(nil)).Elem()
schemaTypeBool = schema.SpawnBool("Bool")
schemaTypeInt = schema.SpawnInt("Int")
schemaTypeFloat = schema.SpawnFloat("Float")
schemaTypeString = schema.SpawnString("String")
schemaTypeBytes = schema.SpawnBytes("Bytes")
schemaTypeLink = schema.SpawnLink("Link")
schemaTypeAny = schema.SpawnAny("Any")
)
// Consider exposing these APIs later, if they might be useful.
type seenEntry struct {
goType reflect.Type
schemaType schema.Type
}
// verifyCompatibility is the primary way we check that the schema type(s)
// matches the Go type(s); so we do this before we can proceed operating on it.
// verifyCompatibility doesn't return an error, it panics—the errors here are
// not runtime errors, they're programmer errors because your schema doesn't
// match your Go type
func verifyCompatibility(cfg config, seen map[seenEntry]bool, goType reflect.Type, schemaType schema.Type) {
// TODO(mvdan): support **T as well?
if goType.Kind() == reflect.Ptr {
goType = goType.Elem()
}
// Avoid endless loops.
//
// TODO(mvdan): this is easy but fairly allocation-happy.
// Plus, one map per call means we don't reuse work.
if seen[seenEntry{goType, schemaType}] {
return
}
seen[seenEntry{goType, schemaType}] = true
doPanic := func(format string, args ...interface{}) {
panicFormat := "bindnode: schema type %s is not compatible with Go type %s"
panicArgs := []interface{}{schemaType.Name(), goType.String()}
if format != "" {
panicFormat += ": " + format
}
panicArgs = append(panicArgs, args...)
panic(fmt.Sprintf(panicFormat, panicArgs...))
}
switch schemaType := schemaType.(type) {
case *schema.TypeBool:
if customConverter := cfg.converterForType(goType); customConverter != nil {
if customConverter.kind != schema.TypeKind_Bool {
doPanic("kind mismatch; custom converter for type is not for Bool")
}
} else if goType.Kind() != reflect.Bool {
doPanic("kind mismatch; need boolean")
}
case *schema.TypeInt:
if customConverter := cfg.converterForType(goType); customConverter != nil {
if customConverter.kind != schema.TypeKind_Int {
doPanic("kind mismatch; custom converter for type is not for Int")
}
} else if kind := goType.Kind(); !kindInt[kind] && !kindUint[kind] {
doPanic("kind mismatch; need integer")
}
case *schema.TypeFloat:
if customConverter := cfg.converterForType(goType); customConverter != nil {
if customConverter.kind != schema.TypeKind_Float {
doPanic("kind mismatch; custom converter for type is not for Float")
}
} else {
switch goType.Kind() {
case reflect.Float32, reflect.Float64:
default:
doPanic("kind mismatch; need float")
}
}
case *schema.TypeString:
// TODO: allow []byte?
if customConverter := cfg.converterForType(goType); customConverter != nil {
if customConverter.kind != schema.TypeKind_String {
doPanic("kind mismatch; custom converter for type is not for String")
}
} else if goType.Kind() != reflect.String {
doPanic("kind mismatch; need string")
}
case *schema.TypeBytes:
// TODO: allow string?
if customConverter := cfg.converterForType(goType); customConverter != nil {
if customConverter.kind != schema.TypeKind_Bytes {
doPanic("kind mismatch; custom converter for type is not for Bytes")
}
} else if goType.Kind() != reflect.Slice {
doPanic("kind mismatch; need slice of bytes")
} else if goType.Elem().Kind() != reflect.Uint8 {
doPanic("kind mismatch; need slice of bytes")
}
case *schema.TypeEnum:
if _, ok := schemaType.RepresentationStrategy().(schema.EnumRepresentation_Int); ok {
if kind := goType.Kind(); kind != reflect.String && !kindInt[kind] && !kindUint[kind] {
doPanic("kind mismatch; need string or integer")
}
} else {
if goType.Kind() != reflect.String {
doPanic("kind mismatch; need string")
}
}
case *schema.TypeList:
if goType.Kind() != reflect.Slice {
doPanic("kind mismatch; need slice")
}
goType = goType.Elem()
if schemaType.ValueIsNullable() {
if ptr, nilable := ptrOrNilable(goType.Kind()); !nilable {
doPanic("nullable types must be nilable")
} else if ptr {
goType = goType.Elem()
}
}
verifyCompatibility(cfg, seen, goType, schemaType.ValueType())
case *schema.TypeMap:
// struct {
// Keys []K
// Values map[K]V
// }
if goType.Kind() != reflect.Struct {
doPanic("kind mismatch; need struct{Keys []K; Values map[K]V}")
}
if goType.NumField() != 2 {
doPanic("%d vs 2 fields", goType.NumField())
}
fieldKeys := goType.Field(0)
if fieldKeys.Type.Kind() != reflect.Slice {
doPanic("kind mismatch; need struct{Keys []K; Values map[K]V}")
}
verifyCompatibility(cfg, seen, fieldKeys.Type.Elem(), schemaType.KeyType())
fieldValues := goType.Field(1)
if fieldValues.Type.Kind() != reflect.Map {
doPanic("kind mismatch; need struct{Keys []K; Values map[K]V}")
}
keyType := fieldValues.Type.Key()
verifyCompatibility(cfg, seen, keyType, schemaType.KeyType())
elemType := fieldValues.Type.Elem()
if schemaType.ValueIsNullable() {
if ptr, nilable := ptrOrNilable(elemType.Kind()); !nilable {
doPanic("nullable types must be nilable")
} else if ptr {
elemType = elemType.Elem()
}
}
verifyCompatibility(cfg, seen, elemType, schemaType.ValueType())
case *schema.TypeStruct:
if goType.Kind() != reflect.Struct {
doPanic("kind mismatch; need struct")
}
schemaFields := schemaType.Fields()
if goType.NumField() != len(schemaFields) {
doPanic("%d vs %d fields", goType.NumField(), len(schemaFields))
}
for i, schemaField := range schemaFields {
schemaType := schemaField.Type()
goType := goType.Field(i).Type
switch {
case schemaField.IsOptional() && schemaField.IsNullable():
// TODO: https://github.com/ipld/go-ipld-prime/issues/340 will
// help here, to avoid the double pointer. We can't use nilable
// but non-pointer types because that's just one "nil" state.
// TODO: deal with custom converters in this case
if goType.Kind() != reflect.Ptr {
doPanic("optional and nullable fields must use double pointers (**)")
}
goType = goType.Elem()
if goType.Kind() != reflect.Ptr {
doPanic("optional and nullable fields must use double pointers (**)")
}
goType = goType.Elem()
case schemaField.IsOptional():
if ptr, nilable := ptrOrNilable(goType.Kind()); !nilable {
doPanic("optional fields must be nilable")
} else if ptr {
goType = goType.Elem()
}
case schemaField.IsNullable():
if ptr, nilable := ptrOrNilable(goType.Kind()); !nilable {
if customConverter := cfg.converterForType(goType); customConverter == nil {
doPanic("nullable fields must be nilable")
}
} else if ptr {
goType = goType.Elem()
}
}
verifyCompatibility(cfg, seen, goType, schemaType)
}
case *schema.TypeUnion:
if goType.Kind() != reflect.Struct {
doPanic("kind mismatch; need struct for an union")
}
schemaMembers := schemaType.Members()
if goType.NumField() != len(schemaMembers) {
doPanic("%d vs %d members", goType.NumField(), len(schemaMembers))
}
for i, schemaType := range schemaMembers {
goType := goType.Field(i).Type
if ptr, nilable := ptrOrNilable(goType.Kind()); !nilable {
doPanic("union members must be nilable")
} else if ptr {
goType = goType.Elem()
}
verifyCompatibility(cfg, seen, goType, schemaType)
}
case *schema.TypeLink:
if customConverter := cfg.converterForType(goType); customConverter != nil {
if customConverter.kind != schema.TypeKind_Link {
doPanic("kind mismatch; custom converter for type is not for Link")
}
} else if goType != goTypeLink && goType != goTypeCidLink && goType != goTypeCid {
doPanic("links in Go must be datamodel.Link, cidlink.Link, or cid.Cid")
}
case *schema.TypeAny:
if customConverter := cfg.converterForType(goType); customConverter != nil {
if customConverter.kind != schema.TypeKind_Any {
doPanic("kind mismatch; custom converter for type is not for Any")
}
} else if goType != goTypeNode {
doPanic("Any in Go must be datamodel.Node")
}
default:
panic(fmt.Sprintf("%T", schemaType))
}
}
func ptrOrNilable(kind reflect.Kind) (ptr, nilable bool) {
switch kind {
case reflect.Ptr:
return true, true
case reflect.Interface, reflect.Map, reflect.Slice:
return false, true
default:
return false, false
}
}
// If we recurse past a large number of levels, we're mostly stuck in a loop.
// Prevent burning CPU or causing OOM crashes.
// If a user really wrote an IPLD schema or Go type with such deep nesting,
// it's likely they are trying to abuse the system as well.
const maxRecursionLevel = 1 << 10
type inferredStatus int
const (
_ inferredStatus = iota
inferringInProcess
inferringDone
)
// inferGoType can build a Go type given a schema
func inferGoType(typ schema.Type, status map[schema.TypeName]inferredStatus, level int) reflect.Type {
if level > maxRecursionLevel {
panic(fmt.Sprintf("inferGoType: refusing to recurse past %d levels", maxRecursionLevel))
}
name := typ.Name()
if status[name] == inferringInProcess {
panic("bindnode: inferring Go types from cyclic schemas is not supported since Go reflection does not support creating named types")
}
status[name] = inferringInProcess
defer func() { status[name] = inferringDone }()
switch typ := typ.(type) {
case *schema.TypeBool:
return goTypeBool
case *schema.TypeInt:
return goTypeInt
case *schema.TypeFloat:
return goTypeFloat
case *schema.TypeString:
return goTypeString
case *schema.TypeBytes:
return goTypeBytes
case *schema.TypeStruct:
fields := typ.Fields()
fieldsGo := make([]reflect.StructField, len(fields))
for i, field := range fields {
ftypGo := inferGoType(field.Type(), status, level+1)
if field.IsNullable() {
ftypGo = reflect.PtrTo(ftypGo)
}
if field.IsOptional() {
ftypGo = reflect.PtrTo(ftypGo)
}
fieldsGo[i] = reflect.StructField{
Name: fieldNameFromSchema(field.Name()),
Type: ftypGo,
}
}
return reflect.StructOf(fieldsGo)
case *schema.TypeMap:
ktyp := inferGoType(typ.KeyType(), status, level+1)
vtyp := inferGoType(typ.ValueType(), status, level+1)
if typ.ValueIsNullable() {
vtyp = reflect.PtrTo(vtyp)
}
// We need an extra field to keep the map ordered,
// since IPLD maps must have stable iteration order.
// We could sort when iterating, but that's expensive.
// Keeping the insertion order is easy and intuitive.
//
// struct {
// Keys []K
// Values map[K]V
// }
fieldsGo := []reflect.StructField{
{
Name: "Keys",
Type: reflect.SliceOf(ktyp),
},
{
Name: "Values",
Type: reflect.MapOf(ktyp, vtyp),
},
}
return reflect.StructOf(fieldsGo)
case *schema.TypeList:
etyp := inferGoType(typ.ValueType(), status, level+1)
if typ.ValueIsNullable() {
etyp = reflect.PtrTo(etyp)
}
return reflect.SliceOf(etyp)
case *schema.TypeUnion:
// type goUnion struct {
// Type1 *Type1
// Type2 *Type2
// ...
// }
members := typ.Members()
fieldsGo := make([]reflect.StructField, len(members))
for i, ftyp := range members {
ftypGo := inferGoType(ftyp, status, level+1)
fieldsGo[i] = reflect.StructField{
Name: fieldNameFromSchema(ftyp.Name()),
Type: reflect.PtrTo(ftypGo),
}
}
return reflect.StructOf(fieldsGo)
case *schema.TypeLink:
return goTypeLink
case *schema.TypeEnum:
// TODO: generate int for int reprs by default?
return goTypeString
case *schema.TypeAny:
return goTypeNode
case nil:
panic("bindnode: unexpected nil schema.Type")
}
panic(fmt.Sprintf("%T", typ))
}
// from IPLD Schema field names like "foo" to Go field names like "Foo".
func fieldNameFromSchema(name string) string {
fieldName := strings.Title(name) //lint:ignore SA1019 cases.Title doesn't work for this
if !token.IsIdentifier(fieldName) {
panic(fmt.Sprintf("bindnode: inferred field name %q is not a valid Go identifier", fieldName))
}
return fieldName
}
var defaultTypeSystem schema.TypeSystem
func init() {
defaultTypeSystem.Init()
defaultTypeSystem.Accumulate(schemaTypeBool)
defaultTypeSystem.Accumulate(schemaTypeInt)
defaultTypeSystem.Accumulate(schemaTypeFloat)
defaultTypeSystem.Accumulate(schemaTypeString)
defaultTypeSystem.Accumulate(schemaTypeBytes)
defaultTypeSystem.Accumulate(schemaTypeLink)
defaultTypeSystem.Accumulate(schemaTypeAny)
}
// TODO: support IPLD maps and unions in inferSchema
// TODO: support bringing your own TypeSystem?
// TODO: we should probably avoid re-spawning the same types if the TypeSystem
// has them, and test that that works as expected
// inferSchema can build a schema from a Go type
func inferSchema(typ reflect.Type, level int) schema.Type {
if level > maxRecursionLevel {
panic(fmt.Sprintf("inferSchema: refusing to recurse past %d levels", maxRecursionLevel))
}
switch typ.Kind() {
case reflect.Bool:
return schemaTypeBool
case reflect.Int64:
return schemaTypeInt
case reflect.Float64:
return schemaTypeFloat
case reflect.String:
return schemaTypeString
case reflect.Struct:
// these types must match exactly since we need symmetry of being able to
// get the values an also assign values to them
if typ == goTypeCid || typ == goTypeCidLink {
return schemaTypeLink
}
fieldsSchema := make([]schema.StructField, typ.NumField())
for i := range fieldsSchema {
field := typ.Field(i)
ftyp := field.Type
ftypSchema := inferSchema(ftyp, level+1)
fieldsSchema[i] = schema.SpawnStructField(
field.Name, // TODO: allow configuring the name with tags
ftypSchema.Name(),
// TODO: support nullable/optional with tags
false,
false,
)
}
name := typ.Name()
if name == "" {
panic("TODO: anonymous composite types")
}
typSchema := schema.SpawnStruct(name, fieldsSchema, nil)
defaultTypeSystem.Accumulate(typSchema)
return typSchema
case reflect.Slice:
if typ.Elem().Kind() == reflect.Uint8 {
// Special case for []byte.
return schemaTypeBytes
}
nullable := false
if typ.Elem().Kind() == reflect.Ptr {
nullable = true
}
etypSchema := inferSchema(typ.Elem(), level+1)
name := typ.Name()
if name == "" {
name = "List_" + etypSchema.Name()
}
typSchema := schema.SpawnList(name, etypSchema.Name(), nullable)
defaultTypeSystem.Accumulate(typSchema)
return typSchema
case reflect.Interface:
// these types must match exactly since we need symmetry of being able to
// get the values an also assign values to them
if typ == goTypeLink {
return schemaTypeLink
}
if typ == goTypeNode {
return schemaTypeAny
}
panic("bindnode: unable to infer from interface")
}
panic(fmt.Sprintf("bindnode: unable to infer from type %s", typ.Kind().String()))
}
// There are currently 27 reflect.Kind iota values,
// so 32 should be plenty to ensure we don't panic in practice.
var kindInt = [32]bool{
reflect.Int: true,
reflect.Int8: true,
reflect.Int16: true,
reflect.Int32: true,
reflect.Int64: true,
}
var kindUint = [32]bool{
reflect.Uint: true,
reflect.Uint8: true,
reflect.Uint16: true,
reflect.Uint32: true,
reflect.Uint64: true,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
node mixins and how to use them
===============================
These mixins are here to:
1. reduce the amount of code you need to write to create a new Node implementation, and
2. standardize a lot of the error handling for common cases (especially, around kinds).
"Reduce the amount of code" also has an application in codegen,
where while it doesn't save any human effort, it does reduce GLOC size.
(Or more precisely, it doesn't save *lines*, since we use them in verbose style,
but it does make those lines an awful lot shorter.)
Note that these mixins are _not_ particularly here to help with performance.
- all `ErrWrongKind` error are returned by value, which means a `runtime.convT2I` which means a heap allocation.
The error paths will therefore never be "fast"; it will *always* be cheaper
to check `kind` in advance than to probe and handle errors, if efficiency is your goal.
- in general, there's really no way to improve upon the performance of having these methods simply writen directlyon your type.
These mixins will affect struct size if you use them via embed.
They can also be used without any effect on struct size if used more verbosely.
The binary/assembly output size is not affected by use of the mixins.
(If using them verbosely -- e.g. still declaring methods on your type
and using `return mixins.Kind{"TypeName"}.Method()` in the method body --
the end result is the inliner kicks in, and the end result is almost
identical binary size.)
Summary:
- SLOC: good, or neutral depending on use
- GLOC: good
- standardized: good
- speed: neutral
- mem size: neutral if used verbosely, bad if used most tersely
- asm size: neutral

View File

@@ -0,0 +1,97 @@
package mixins
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// Bool can be embedded in a struct to provide all the methods that
// have fixed output for any int-kinded nodes.
// (Mostly this includes all the methods which simply return ErrWrongKind.)
// Other methods will still need to be implemented to finish conforming to Node.
//
// To conserve memory and get a TypeName in errors without embedding,
// write methods on your type with a body that simply initializes this struct
// and immediately uses the relevant method;
// this is more verbose in source, but compiles to a tighter result:
// in memory, there's no embed; and in runtime, the calls will be inlined
// and thus have no cost in execution time.
type Bool struct {
TypeName string
}
func (Bool) Kind() datamodel.Kind {
return datamodel.Kind_Bool
}
func (x Bool) LookupByString(string) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByString", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Bool}
}
func (x Bool) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByNode", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Bool}
}
func (x Bool) LookupByIndex(idx int64) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByIndex", AppropriateKind: datamodel.KindSet_JustList, ActualKind: datamodel.Kind_Bool}
}
func (x Bool) LookupBySegment(datamodel.PathSegment) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupBySegment", AppropriateKind: datamodel.KindSet_Recursive, ActualKind: datamodel.Kind_Bool}
}
func (Bool) MapIterator() datamodel.MapIterator {
return nil
}
func (Bool) ListIterator() datamodel.ListIterator {
return nil
}
func (Bool) Length() int64 {
return -1
}
func (Bool) IsAbsent() bool {
return false
}
func (Bool) IsNull() bool {
return false
}
func (x Bool) AsInt() (int64, error) {
return 0, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsInt", AppropriateKind: datamodel.KindSet_JustInt, ActualKind: datamodel.Kind_Bool}
}
func (x Bool) AsFloat() (float64, error) {
return 0, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsFloat", AppropriateKind: datamodel.KindSet_JustFloat, ActualKind: datamodel.Kind_Bool}
}
func (x Bool) AsString() (string, error) {
return "", datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsString", AppropriateKind: datamodel.KindSet_JustString, ActualKind: datamodel.Kind_Bool}
}
func (x Bool) AsBytes() ([]byte, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBytes", AppropriateKind: datamodel.KindSet_JustBytes, ActualKind: datamodel.Kind_Bool}
}
func (x Bool) AsLink() (datamodel.Link, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsLink", AppropriateKind: datamodel.KindSet_JustLink, ActualKind: datamodel.Kind_Bool}
}
// BoolAssembler has similar purpose as Bool, but for (you guessed it)
// the NodeAssembler interface rather than the Node interface.
type BoolAssembler struct {
TypeName string
}
func (x BoolAssembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginMap", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Bool}
}
func (x BoolAssembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginList", AppropriateKind: datamodel.KindSet_JustList, ActualKind: datamodel.Kind_Bool}
}
func (x BoolAssembler) AssignNull() error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignNull", AppropriateKind: datamodel.KindSet_JustNull, ActualKind: datamodel.Kind_Bool}
}
func (x BoolAssembler) AssignInt(int64) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignInt", AppropriateKind: datamodel.KindSet_JustInt, ActualKind: datamodel.Kind_Bool}
}
func (x BoolAssembler) AssignFloat(float64) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignFloat", AppropriateKind: datamodel.KindSet_JustFloat, ActualKind: datamodel.Kind_Bool}
}
func (x BoolAssembler) AssignString(string) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignString", AppropriateKind: datamodel.KindSet_JustString, ActualKind: datamodel.Kind_Bool}
}
func (x BoolAssembler) AssignBytes([]byte) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBytes", AppropriateKind: datamodel.KindSet_JustBytes, ActualKind: datamodel.Kind_Bool}
}
func (x BoolAssembler) AssignLink(datamodel.Link) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignLink", AppropriateKind: datamodel.KindSet_JustLink, ActualKind: datamodel.Kind_Bool}
}

View File

@@ -0,0 +1,97 @@
package mixins
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// Bytes can be embedded in a struct to provide all the methods that
// have fixed output for any int-kinded nodes.
// (Mostly this includes all the methods which simply return ErrWrongKind.)
// Other methods will still need to be implemented to finish conforming to Node.
//
// To conserve memory and get a TypeName in errors without embedding,
// write methods on your type with a body that simply initializes this struct
// and immediately uses the relevant method;
// this is more verbose in source, but compiles to a tighter result:
// in memory, there's no embed; and in runtime, the calls will be inlined
// and thus have no cost in execution time.
type Bytes struct {
TypeName string
}
func (Bytes) Kind() datamodel.Kind {
return datamodel.Kind_Bytes
}
func (x Bytes) LookupByString(string) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByString", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Bytes}
}
func (x Bytes) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByNode", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Bytes}
}
func (x Bytes) LookupByIndex(idx int64) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByIndex", AppropriateKind: datamodel.KindSet_JustList, ActualKind: datamodel.Kind_Bytes}
}
func (x Bytes) LookupBySegment(datamodel.PathSegment) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupBySegment", AppropriateKind: datamodel.KindSet_Recursive, ActualKind: datamodel.Kind_Bytes}
}
func (Bytes) MapIterator() datamodel.MapIterator {
return nil
}
func (Bytes) ListIterator() datamodel.ListIterator {
return nil
}
func (Bytes) Length() int64 {
return -1
}
func (Bytes) IsAbsent() bool {
return false
}
func (Bytes) IsNull() bool {
return false
}
func (x Bytes) AsBool() (bool, error) {
return false, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBool", AppropriateKind: datamodel.KindSet_JustBool, ActualKind: datamodel.Kind_Bytes}
}
func (x Bytes) AsInt() (int64, error) {
return 0, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsInt", AppropriateKind: datamodel.KindSet_JustInt, ActualKind: datamodel.Kind_Bytes}
}
func (x Bytes) AsFloat() (float64, error) {
return 0, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsFloat", AppropriateKind: datamodel.KindSet_JustFloat, ActualKind: datamodel.Kind_Bytes}
}
func (x Bytes) AsString() (string, error) {
return "", datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsString", AppropriateKind: datamodel.KindSet_JustString, ActualKind: datamodel.Kind_Bytes}
}
func (x Bytes) AsLink() (datamodel.Link, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsLink", AppropriateKind: datamodel.KindSet_JustLink, ActualKind: datamodel.Kind_Bytes}
}
// BytesAssembler has similar purpose as Bytes, but for (you guessed it)
// the NodeAssembler interface rather than the Node interface.
type BytesAssembler struct {
TypeName string
}
func (x BytesAssembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginMap", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Bytes}
}
func (x BytesAssembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginList", AppropriateKind: datamodel.KindSet_JustList, ActualKind: datamodel.Kind_Bytes}
}
func (x BytesAssembler) AssignNull() error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignNull", AppropriateKind: datamodel.KindSet_JustNull, ActualKind: datamodel.Kind_Bytes}
}
func (x BytesAssembler) AssignBool(bool) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBool", AppropriateKind: datamodel.KindSet_JustBool, ActualKind: datamodel.Kind_Bytes}
}
func (x BytesAssembler) AssignInt(int64) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignInt", AppropriateKind: datamodel.KindSet_JustInt, ActualKind: datamodel.Kind_Bytes}
}
func (x BytesAssembler) AssignFloat(float64) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignFloat", AppropriateKind: datamodel.KindSet_JustFloat, ActualKind: datamodel.Kind_Bytes}
}
func (x BytesAssembler) AssignString(string) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignString", AppropriateKind: datamodel.KindSet_JustString, ActualKind: datamodel.Kind_Bytes}
}
func (x BytesAssembler) AssignLink(datamodel.Link) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignLink", AppropriateKind: datamodel.KindSet_JustLink, ActualKind: datamodel.Kind_Bytes}
}

View File

@@ -0,0 +1,40 @@
package mixins
// This file is a little different than most of its siblings in this package.
// It's not really much of a "mixin". More of a util function junkdrawer.
//
// Implementations of Data Model Nodes are unlikely to need these.
// Implementations of Schema-level Node *are* likely to need these, however.
//
// Our codegen implementation emits calls to these functions.
// (And having these functions in a package that's already an unconditional
// import in files emitted by codegen makes the codegen significantly simpler.)
import (
"fmt"
"strings"
)
// SplitExact is much like strings.Split but will error if the number of
// substrings is other than the expected count.
//
// SplitExact is used by the 'stringjoin' representation for structs.
//
// The 'count' parameter is a length. In other words, if you expect
// the zero'th index to be present in the result, you should ask for
// a count of at least '1'.
// Using this function with 'count' less than 2 is rather strange.
func SplitExact(s string, sep string, count int) ([]string, error) {
ss := strings.Split(s, sep)
if len(ss) != count {
return nil, fmt.Errorf("expected %d instances of the delimiter, found %d", count-1, len(ss)-1)
}
return ss, nil
}
// SplitN is an alias of strings.SplitN, which is only present here to
// make it usable in codegen packages without requiring conditional imports
// in the generation process.
func SplitN(s, sep string, n int) []string {
return strings.SplitN(s, sep, n)
}

View File

@@ -0,0 +1,97 @@
package mixins
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// Float can be embedded in a struct to provide all the methods that
// have fixed output for any int-kinded nodes.
// (Mostly this includes all the methods which simply return ErrWrongKind.)
// Other methods will still need to be implemented to finish conforming to Node.
//
// To conserve memory and get a TypeName in errors without embedding,
// write methods on your type with a body that simply initializes this struct
// and immediately uses the relevant method;
// this is more verbose in source, but compiles to a tighter result:
// in memory, there's no embed; and in runtime, the calls will be inlined
// and thus have no cost in execution time.
type Float struct {
TypeName string
}
func (Float) Kind() datamodel.Kind {
return datamodel.Kind_Float
}
func (x Float) LookupByString(string) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByString", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Float}
}
func (x Float) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByNode", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Float}
}
func (x Float) LookupByIndex(idx int64) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByIndex", AppropriateKind: datamodel.KindSet_JustList, ActualKind: datamodel.Kind_Float}
}
func (x Float) LookupBySegment(datamodel.PathSegment) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupBySegment", AppropriateKind: datamodel.KindSet_Recursive, ActualKind: datamodel.Kind_Float}
}
func (Float) MapIterator() datamodel.MapIterator {
return nil
}
func (Float) ListIterator() datamodel.ListIterator {
return nil
}
func (Float) Length() int64 {
return -1
}
func (Float) IsAbsent() bool {
return false
}
func (Float) IsNull() bool {
return false
}
func (x Float) AsBool() (bool, error) {
return false, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBool", AppropriateKind: datamodel.KindSet_JustBool, ActualKind: datamodel.Kind_Float}
}
func (x Float) AsInt() (int64, error) {
return 0, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsInt", AppropriateKind: datamodel.KindSet_JustInt, ActualKind: datamodel.Kind_Float}
}
func (x Float) AsString() (string, error) {
return "", datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsString", AppropriateKind: datamodel.KindSet_JustString, ActualKind: datamodel.Kind_Float}
}
func (x Float) AsBytes() ([]byte, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBytes", AppropriateKind: datamodel.KindSet_JustBytes, ActualKind: datamodel.Kind_Float}
}
func (x Float) AsLink() (datamodel.Link, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsLink", AppropriateKind: datamodel.KindSet_JustLink, ActualKind: datamodel.Kind_Float}
}
// FloatAssembler has similar purpose as Float, but for (you guessed it)
// the NodeAssembler interface rather than the Node interface.
type FloatAssembler struct {
TypeName string
}
func (x FloatAssembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginMap", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Float}
}
func (x FloatAssembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginList", AppropriateKind: datamodel.KindSet_JustList, ActualKind: datamodel.Kind_Float}
}
func (x FloatAssembler) AssignNull() error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignNull", AppropriateKind: datamodel.KindSet_JustNull, ActualKind: datamodel.Kind_Float}
}
func (x FloatAssembler) AssignBool(bool) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBool", AppropriateKind: datamodel.KindSet_JustBool, ActualKind: datamodel.Kind_Float}
}
func (x FloatAssembler) AssignInt(int64) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignInt", AppropriateKind: datamodel.KindSet_JustInt, ActualKind: datamodel.Kind_Float}
}
func (x FloatAssembler) AssignString(string) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignString", AppropriateKind: datamodel.KindSet_JustString, ActualKind: datamodel.Kind_Float}
}
func (x FloatAssembler) AssignBytes([]byte) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBytes", AppropriateKind: datamodel.KindSet_JustBytes, ActualKind: datamodel.Kind_Float}
}
func (x FloatAssembler) AssignLink(datamodel.Link) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignLink", AppropriateKind: datamodel.KindSet_JustLink, ActualKind: datamodel.Kind_Float}
}

View File

@@ -0,0 +1,97 @@
package mixins
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// Int can be embedded in a struct to provide all the methods that
// have fixed output for any int-kinded nodes.
// (Mostly this includes all the methods which simply return ErrWrongKind.)
// Other methods will still need to be implemented to finish conforming to Node.
//
// To conserve memory and get a TypeName in errors without embedding,
// write methods on your type with a body that simply initializes this struct
// and immediately uses the relevant method;
// this is more verbose in source, but compiles to a tighter result:
// in memory, there's no embed; and in runtime, the calls will be inlined
// and thus have no cost in execution time.
type Int struct {
TypeName string
}
func (Int) Kind() datamodel.Kind {
return datamodel.Kind_Int
}
func (x Int) LookupByString(string) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByString", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Int}
}
func (x Int) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByNode", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Int}
}
func (x Int) LookupByIndex(idx int64) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByIndex", AppropriateKind: datamodel.KindSet_JustList, ActualKind: datamodel.Kind_Int}
}
func (x Int) LookupBySegment(datamodel.PathSegment) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupBySegment", AppropriateKind: datamodel.KindSet_Recursive, ActualKind: datamodel.Kind_Int}
}
func (Int) MapIterator() datamodel.MapIterator {
return nil
}
func (Int) ListIterator() datamodel.ListIterator {
return nil
}
func (Int) Length() int64 {
return -1
}
func (Int) IsAbsent() bool {
return false
}
func (Int) IsNull() bool {
return false
}
func (x Int) AsBool() (bool, error) {
return false, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBool", AppropriateKind: datamodel.KindSet_JustBool, ActualKind: datamodel.Kind_Int}
}
func (x Int) AsFloat() (float64, error) {
return 0, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsFloat", AppropriateKind: datamodel.KindSet_JustFloat, ActualKind: datamodel.Kind_Int}
}
func (x Int) AsString() (string, error) {
return "", datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsString", AppropriateKind: datamodel.KindSet_JustString, ActualKind: datamodel.Kind_Int}
}
func (x Int) AsBytes() ([]byte, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBytes", AppropriateKind: datamodel.KindSet_JustBytes, ActualKind: datamodel.Kind_Int}
}
func (x Int) AsLink() (datamodel.Link, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsLink", AppropriateKind: datamodel.KindSet_JustLink, ActualKind: datamodel.Kind_Int}
}
// IntAssembler has similar purpose as Int, but for (you guessed it)
// the NodeAssembler interface rather than the Node interface.
type IntAssembler struct {
TypeName string
}
func (x IntAssembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginMap", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Int}
}
func (x IntAssembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginList", AppropriateKind: datamodel.KindSet_JustList, ActualKind: datamodel.Kind_Int}
}
func (x IntAssembler) AssignNull() error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignNull", AppropriateKind: datamodel.KindSet_JustNull, ActualKind: datamodel.Kind_Int}
}
func (x IntAssembler) AssignBool(bool) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBool", AppropriateKind: datamodel.KindSet_JustBool, ActualKind: datamodel.Kind_Int}
}
func (x IntAssembler) AssignFloat(float64) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignFloat", AppropriateKind: datamodel.KindSet_JustFloat, ActualKind: datamodel.Kind_Int}
}
func (x IntAssembler) AssignString(string) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignString", AppropriateKind: datamodel.KindSet_JustString, ActualKind: datamodel.Kind_Int}
}
func (x IntAssembler) AssignBytes([]byte) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBytes", AppropriateKind: datamodel.KindSet_JustBytes, ActualKind: datamodel.Kind_Int}
}
func (x IntAssembler) AssignLink(datamodel.Link) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignLink", AppropriateKind: datamodel.KindSet_JustLink, ActualKind: datamodel.Kind_Int}
}

View File

@@ -0,0 +1,97 @@
package mixins
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// Link can be embedded in a struct to provide all the methods that
// have fixed output for any int-kinded nodes.
// (Mostly this includes all the methods which simply return ErrWrongKind.)
// Other methods will still need to be implemented to finish conforming to Node.
//
// To conserve memory and get a TypeName in errors without embedding,
// write methods on your type with a body that simply initializes this struct
// and immediately uses the relevant method;
// this is more verbose in source, but compiles to a tighter result:
// in memory, there's no embed; and in runtime, the calls will be inlined
// and thus have no cost in execution time.
type Link struct {
TypeName string
}
func (Link) Kind() datamodel.Kind {
return datamodel.Kind_Link
}
func (x Link) LookupByString(string) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByString", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Link}
}
func (x Link) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByNode", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Link}
}
func (x Link) LookupByIndex(idx int64) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByIndex", AppropriateKind: datamodel.KindSet_JustList, ActualKind: datamodel.Kind_Link}
}
func (x Link) LookupBySegment(datamodel.PathSegment) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupBySegment", AppropriateKind: datamodel.KindSet_Recursive, ActualKind: datamodel.Kind_Link}
}
func (Link) MapIterator() datamodel.MapIterator {
return nil
}
func (Link) ListIterator() datamodel.ListIterator {
return nil
}
func (Link) Length() int64 {
return -1
}
func (Link) IsAbsent() bool {
return false
}
func (Link) IsNull() bool {
return false
}
func (x Link) AsBool() (bool, error) {
return false, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBool", AppropriateKind: datamodel.KindSet_JustBool, ActualKind: datamodel.Kind_Link}
}
func (x Link) AsInt() (int64, error) {
return 0, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsInt", AppropriateKind: datamodel.KindSet_JustInt, ActualKind: datamodel.Kind_Link}
}
func (x Link) AsFloat() (float64, error) {
return 0, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsFloat", AppropriateKind: datamodel.KindSet_JustFloat, ActualKind: datamodel.Kind_Link}
}
func (x Link) AsString() (string, error) {
return "", datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsString", AppropriateKind: datamodel.KindSet_JustString, ActualKind: datamodel.Kind_Link}
}
func (x Link) AsBytes() ([]byte, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBytes", AppropriateKind: datamodel.KindSet_JustBytes, ActualKind: datamodel.Kind_Link}
}
// LinkAssembler has similar purpose as Link, but for (you guessed it)
// the NodeAssembler interface rather than the Node interface.
type LinkAssembler struct {
TypeName string
}
func (x LinkAssembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginMap", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_Link}
}
func (x LinkAssembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginList", AppropriateKind: datamodel.KindSet_JustList, ActualKind: datamodel.Kind_Link}
}
func (x LinkAssembler) AssignNull() error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignNull", AppropriateKind: datamodel.KindSet_JustNull, ActualKind: datamodel.Kind_Link}
}
func (x LinkAssembler) AssignBool(bool) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBool", AppropriateKind: datamodel.KindSet_JustBool, ActualKind: datamodel.Kind_Link}
}
func (x LinkAssembler) AssignInt(int64) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignInt", AppropriateKind: datamodel.KindSet_JustInt, ActualKind: datamodel.Kind_Link}
}
func (x LinkAssembler) AssignFloat(float64) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignFloat", AppropriateKind: datamodel.KindSet_JustFloat, ActualKind: datamodel.Kind_Link}
}
func (x LinkAssembler) AssignString(string) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignString", AppropriateKind: datamodel.KindSet_JustString, ActualKind: datamodel.Kind_Link}
}
func (x LinkAssembler) AssignBytes([]byte) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBytes", AppropriateKind: datamodel.KindSet_JustBytes, ActualKind: datamodel.Kind_Link}
}

View File

@@ -0,0 +1,88 @@
package mixins
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// List can be embedded in a struct to provide all the methods that
// have fixed output for any int-kinded nodes.
// (Mostly this includes all the methods which simply return ErrWrongKind.)
// Other methods will still need to be implemented to finish conforming to Node.
//
// To conserve memory and get a TypeName in errors without embedding,
// write methods on your type with a body that simply initializes this struct
// and immediately uses the relevant method;
// this is more verbose in source, but compiles to a tighter result:
// in memory, there's no embed; and in runtime, the calls will be inlined
// and thus have no cost in execution time.
type List struct {
TypeName string
}
func (List) Kind() datamodel.Kind {
return datamodel.Kind_List
}
func (x List) LookupByString(string) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByString", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_List}
}
func (x List) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByNode", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_List}
}
func (List) MapIterator() datamodel.MapIterator {
return nil
}
func (List) IsAbsent() bool {
return false
}
func (List) IsNull() bool {
return false
}
func (x List) AsBool() (bool, error) {
return false, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBool", AppropriateKind: datamodel.KindSet_JustBool, ActualKind: datamodel.Kind_List}
}
func (x List) AsInt() (int64, error) {
return 0, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsInt", AppropriateKind: datamodel.KindSet_JustInt, ActualKind: datamodel.Kind_List}
}
func (x List) AsFloat() (float64, error) {
return 0, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsFloat", AppropriateKind: datamodel.KindSet_JustFloat, ActualKind: datamodel.Kind_List}
}
func (x List) AsString() (string, error) {
return "", datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsString", AppropriateKind: datamodel.KindSet_JustString, ActualKind: datamodel.Kind_List}
}
func (x List) AsBytes() ([]byte, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBytes", AppropriateKind: datamodel.KindSet_JustBytes, ActualKind: datamodel.Kind_List}
}
func (x List) AsLink() (datamodel.Link, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsLink", AppropriateKind: datamodel.KindSet_JustLink, ActualKind: datamodel.Kind_List}
}
// ListAssembler has similar purpose as List, but for (you guessed it)
// the NodeAssembler interface rather than the Node interface.
type ListAssembler struct {
TypeName string
}
func (x ListAssembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginMap", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_List}
}
func (x ListAssembler) AssignNull() error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignNull", AppropriateKind: datamodel.KindSet_JustNull, ActualKind: datamodel.Kind_List}
}
func (x ListAssembler) AssignBool(bool) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBool", AppropriateKind: datamodel.KindSet_JustBool, ActualKind: datamodel.Kind_List}
}
func (x ListAssembler) AssignInt(int64) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignInt", AppropriateKind: datamodel.KindSet_JustInt, ActualKind: datamodel.Kind_List}
}
func (x ListAssembler) AssignFloat(float64) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignFloat", AppropriateKind: datamodel.KindSet_JustFloat, ActualKind: datamodel.Kind_List}
}
func (x ListAssembler) AssignString(string) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignString", AppropriateKind: datamodel.KindSet_JustString, ActualKind: datamodel.Kind_List}
}
func (x ListAssembler) AssignBytes([]byte) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBytes", AppropriateKind: datamodel.KindSet_JustBytes, ActualKind: datamodel.Kind_List}
}
func (x ListAssembler) AssignLink(datamodel.Link) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignLink", AppropriateKind: datamodel.KindSet_JustLink, ActualKind: datamodel.Kind_List}
}

View File

@@ -0,0 +1,85 @@
package mixins
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// Map can be embedded in a struct to provide all the methods that
// have fixed output for any map-kinded nodes.
// (Mostly this includes all the methods which simply return ErrWrongKind.)
// Other methods will still need to be implemented to finish conforming to Node.
//
// To conserve memory and get a TypeName in errors without embedding,
// write methods on your type with a body that simply initializes this struct
// and immediately uses the relevant method;
// this is more verbose in source, but compiles to a tighter result:
// in memory, there's no embed; and in runtime, the calls will be inlined
// and thus have no cost in execution time.
type Map struct {
TypeName string
}
func (Map) Kind() datamodel.Kind {
return datamodel.Kind_Map
}
func (x Map) LookupByIndex(idx int64) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByIndex", AppropriateKind: datamodel.KindSet_JustList, ActualKind: datamodel.Kind_Map}
}
func (Map) ListIterator() datamodel.ListIterator {
return nil
}
func (Map) IsAbsent() bool {
return false
}
func (Map) IsNull() bool {
return false
}
func (x Map) AsBool() (bool, error) {
return false, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBool", AppropriateKind: datamodel.KindSet_JustBool, ActualKind: datamodel.Kind_Map}
}
func (x Map) AsInt() (int64, error) {
return 0, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsInt", AppropriateKind: datamodel.KindSet_JustInt, ActualKind: datamodel.Kind_Map}
}
func (x Map) AsFloat() (float64, error) {
return 0, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsFloat", AppropriateKind: datamodel.KindSet_JustFloat, ActualKind: datamodel.Kind_Map}
}
func (x Map) AsString() (string, error) {
return "", datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsString", AppropriateKind: datamodel.KindSet_JustString, ActualKind: datamodel.Kind_Map}
}
func (x Map) AsBytes() ([]byte, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBytes", AppropriateKind: datamodel.KindSet_JustBytes, ActualKind: datamodel.Kind_Map}
}
func (x Map) AsLink() (datamodel.Link, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsLink", AppropriateKind: datamodel.KindSet_JustLink, ActualKind: datamodel.Kind_Map}
}
// MapAssembler has similar purpose as Map, but for (you guessed it)
// the NodeAssembler interface rather than the Node interface.
type MapAssembler struct {
TypeName string
}
func (x MapAssembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginList", AppropriateKind: datamodel.KindSet_JustList, ActualKind: datamodel.Kind_Map}
}
func (x MapAssembler) AssignNull() error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignNull", AppropriateKind: datamodel.KindSet_JustNull, ActualKind: datamodel.Kind_Map}
}
func (x MapAssembler) AssignBool(bool) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBool", AppropriateKind: datamodel.KindSet_JustBool, ActualKind: datamodel.Kind_Map}
}
func (x MapAssembler) AssignInt(int64) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignInt", AppropriateKind: datamodel.KindSet_JustInt, ActualKind: datamodel.Kind_Map}
}
func (x MapAssembler) AssignFloat(float64) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignFloat", AppropriateKind: datamodel.KindSet_JustFloat, ActualKind: datamodel.Kind_Map}
}
func (x MapAssembler) AssignString(string) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignString", AppropriateKind: datamodel.KindSet_JustString, ActualKind: datamodel.Kind_Map}
}
func (x MapAssembler) AssignBytes([]byte) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBytes", AppropriateKind: datamodel.KindSet_JustBytes, ActualKind: datamodel.Kind_Map}
}
func (x MapAssembler) AssignLink(datamodel.Link) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignLink", AppropriateKind: datamodel.KindSet_JustLink, ActualKind: datamodel.Kind_Map}
}

View File

@@ -0,0 +1,97 @@
package mixins
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// String can be embedded in a struct to provide all the methods that
// have fixed output for any string-kinded nodes.
// (Mostly this includes all the methods which simply return ErrWrongKind.)
// Other methods will still need to be implemented to finish conforming to Node.
//
// To conserve memory and get a TypeName in errors without embedding,
// write methods on your type with a body that simply initializes this struct
// and immediately uses the relevant method;
// this is more verbose in source, but compiles to a tighter result:
// in memory, there's no embed; and in runtime, the calls will be inlined
// and thus have no cost in execution time.
type String struct {
TypeName string
}
func (String) Kind() datamodel.Kind {
return datamodel.Kind_String
}
func (x String) LookupByString(string) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByString", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_String}
}
func (x String) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByNode", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_String}
}
func (x String) LookupByIndex(idx int64) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByIndex", AppropriateKind: datamodel.KindSet_JustList, ActualKind: datamodel.Kind_String}
}
func (x String) LookupBySegment(datamodel.PathSegment) (datamodel.Node, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupBySegment", AppropriateKind: datamodel.KindSet_Recursive, ActualKind: datamodel.Kind_String}
}
func (String) MapIterator() datamodel.MapIterator {
return nil
}
func (String) ListIterator() datamodel.ListIterator {
return nil
}
func (String) Length() int64 {
return -1
}
func (String) IsAbsent() bool {
return false
}
func (String) IsNull() bool {
return false
}
func (x String) AsBool() (bool, error) {
return false, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBool", AppropriateKind: datamodel.KindSet_JustBool, ActualKind: datamodel.Kind_String}
}
func (x String) AsInt() (int64, error) {
return 0, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsInt", AppropriateKind: datamodel.KindSet_JustInt, ActualKind: datamodel.Kind_String}
}
func (x String) AsFloat() (float64, error) {
return 0, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsFloat", AppropriateKind: datamodel.KindSet_JustFloat, ActualKind: datamodel.Kind_String}
}
func (x String) AsBytes() ([]byte, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBytes", AppropriateKind: datamodel.KindSet_JustBytes, ActualKind: datamodel.Kind_String}
}
func (x String) AsLink() (datamodel.Link, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsLink", AppropriateKind: datamodel.KindSet_JustLink, ActualKind: datamodel.Kind_String}
}
// StringAssembler has similar purpose as String, but for (you guessed it)
// the NodeAssembler interface rather than the Node interface.
type StringAssembler struct {
TypeName string
}
func (x StringAssembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginMap", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: datamodel.Kind_String}
}
func (x StringAssembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
return nil, datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginList", AppropriateKind: datamodel.KindSet_JustList, ActualKind: datamodel.Kind_String}
}
func (x StringAssembler) AssignNull() error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignNull", AppropriateKind: datamodel.KindSet_JustNull, ActualKind: datamodel.Kind_String}
}
func (x StringAssembler) AssignBool(bool) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBool", AppropriateKind: datamodel.KindSet_JustBool, ActualKind: datamodel.Kind_String}
}
func (x StringAssembler) AssignInt(int64) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignInt", AppropriateKind: datamodel.KindSet_JustInt, ActualKind: datamodel.Kind_String}
}
func (x StringAssembler) AssignFloat(float64) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignFloat", AppropriateKind: datamodel.KindSet_JustFloat, ActualKind: datamodel.Kind_String}
}
func (x StringAssembler) AssignBytes([]byte) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBytes", AppropriateKind: datamodel.KindSet_JustBytes, ActualKind: datamodel.Kind_String}
}
func (x StringAssembler) AssignLink(datamodel.Link) error {
return datamodel.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignLink", AppropriateKind: datamodel.KindSet_JustLink, ActualKind: datamodel.Kind_String}
}

View File

@@ -0,0 +1,107 @@
// copy this and remove methods that aren't relevant to your kind.
// this has not been scripted.
// (the first part is trivial; the second part is not; and this updates rarely. https://xkcd.com/1205/ applies.)
package mixins
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// @Kind@ can be embedded in a struct to provide all the methods that
// have fixed output for any int-kinded nodes.
// (Mostly this includes all the methods which simply return ErrWrongKind.)
// Other methods will still need to be implemented to finish conforming to Node.
//
// To conserve memory and get a TypeName in errors without embedding,
// write methods on your type with a body that simply initializes this struct
// and immediately uses the relevant method;
// this is more verbose in source, but compiles to a tighter result:
// in memory, there's no embed; and in runtime, the calls will be inlined
// and thus have no cost in execution time.
type @Kind@ struct {
TypeName string
}
func (@Kind@) Kind() ipld.Kind {
return ipld.Kind_@Kind@
}
func (x @Kind@) LookupByString(string) (ipld.Node, error) {
return nil, ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByString", AppropriateKind: ipld.KindSet_JustMap, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@) LookupByNode(key ipld.Node) (ipld.Node, error) {
return nil, ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByNode", AppropriateKind: ipld.KindSet_JustMap, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@) LookupByIndex(idx int) (ipld.Node, error) {
return nil, ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupByIndex", AppropriateKind: ipld.KindSet_JustList, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@) LookupBySegment(ipld.PathSegment) (ipld.Node, error) {
return nil, ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "LookupBySegment", AppropriateKind: ipld.KindSet_Recursive, ActualKind: ipld.Kind_@Kind@}
}
func (@Kind@) MapIterator() ipld.MapIterator {
return nil
}
func (@Kind@) ListIterator() ipld.ListIterator {
return nil
}
func (@Kind@) Length() int {
return -1
}
func (@Kind@) IsAbsent() bool {
return false
}
func (@Kind@) IsNull() bool {
return false
}
func (x @Kind@) AsBool() (bool, error) {
return false, ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBool", AppropriateKind: ipld.KindSet_JustBool, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@) AsInt() (int, error) {
return 0, ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsInt", AppropriateKind: ipld.KindSet_JustInt, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@) AsFloat() (float64, error) {
return 0, ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsFloat", AppropriateKind: ipld.KindSet_JustFloat, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@) AsString() (string, error) {
return "", ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsString", AppropriateKind: ipld.KindSet_JustString, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@) AsBytes() ([]byte, error) {
return nil, ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsBytes", AppropriateKind: ipld.KindSet_JustBytes, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@) AsLink() (ipld.Link, error) {
return nil, ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "AsLink", AppropriateKind: ipld.KindSet_JustLink, ActualKind: ipld.Kind_@Kind@}
}
// @Kind@Assembler has similar purpose as @Kind@, but for (you guessed it)
// the NodeAssembler interface rather than the Node interface.
type @Kind@Assembler struct {
TypeName string
}
func (x @Kind@Assembler) BeginMap(sizeHint int) (ipld.MapAssembler, error) {
return nil, ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginMap", AppropriateKind: ipld.KindSet_JustMap, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@Assembler) BeginList(sizeHint int) (ipld.ListAssembler, error) {
return nil, ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "BeginList", AppropriateKind: ipld.KindSet_JustList, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@Assembler) AssignNull() error {
return ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignNull", AppropriateKind: ipld.KindSet_JustNull, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@Assembler) AssignBool(bool) error {
return ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBool", AppropriateKind: ipld.KindSet_JustBool, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@Assembler) AssignInt(int) error {
return ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignInt", AppropriateKind: ipld.KindSet_JustInt, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@Assembler) AssignFloat(float64) error {
return ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignFloat", AppropriateKind: ipld.KindSet_JustFloat, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@Assembler) AssignString(string) error {
return ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignString", AppropriateKind: ipld.KindSet_JustString, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@Assembler) AssignBytes([]byte) error {
return ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignBytes", AppropriateKind: ipld.KindSet_JustBytes, ActualKind: ipld.Kind_@Kind@}
}
func (x @Kind@Assembler) AssignLink(ipld.Link) error {
return ipld.ErrWrongKind{TypeName: x.TypeName, MethodName: "AssignLink", AppropriateKind: ipld.KindSet_JustLink, ActualKind: ipld.Kind_@Kind@}
}

13
vendor/github.com/ipld/go-ipld-prime/operations.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
package ipld
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// DeepEqual reports whether x and y are "deeply equal" as IPLD nodes.
// This is similar to reflect.DeepEqual, but based around the Node interface.
//
// This is exactly equivalent to the datamodel.DeepEqual function.
func DeepEqual(x, y Node) bool {
return datamodel.DeepEqual(x, y)
}

43
vendor/github.com/ipld/go-ipld-prime/schema.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
package ipld
import (
"bytes"
"io"
"os"
"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"
)
// LoadSchemaBytes is a shortcut for LoadSchema for the common case where
// the schema is available as a buffer or a string, such as via go:embed.
func LoadSchemaBytes(src []byte) (*schema.TypeSystem, error) {
return LoadSchema("", bytes.NewReader(src))
}
// LoadSchemaBytes is a shortcut for LoadSchema for the common case where
// the schema is a file on disk.
func LoadSchemaFile(path string) (*schema.TypeSystem, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return LoadSchema(path, f)
}
// LoadSchema parses an IPLD Schema in its DSL form
// and compiles its types into a standalone TypeSystem.
func LoadSchema(name string, r io.Reader) (*schema.TypeSystem, error) {
sch, err := schemadsl.Parse(name, r)
if err != nil {
return nil, err
}
ts := new(schema.TypeSystem)
ts.Init()
if err := schemadmt.Compile(ts, sch); err != nil {
return nil, err
}
return ts, nil
}

View File

@@ -0,0 +1,427 @@
package schemadmt
import (
"fmt"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/schema"
)
// Compile transforms a description of a schema in raw data model ("dmt") form
// into a compiled schema.TypeSystem, which is the ready-to-use form.
//
// The first parameter is mutated by this process,
// and the second parameter is the data source.
//
// The compilation process includes first inserting the "prelude" types into the
// schema.TypeSystem -- that is, the "type Bool bool" and "type String string", etc,
// which are generally presumed to be present in any type system.
//
// The compilation process attempts to check the validity of the schema at a logical level as it goes.
// For example, references to type names not present elsewhere in the same schema are now an error
// (even though that has been easily representable in the dmt.Schema form up until this point).
//
// Note that this API is EXPERIMENTAL and will likely change.
// It supports many features of IPLD Schemas,
// but it may yet not support all of them.
// It supports several validations for logical coherency of schemas,
// but may not yet successfully reject all invalid schemas.
func Compile(ts *schema.TypeSystem, node *Schema) error {
// Prelude; probably belongs elsewhere.
{
ts.Accumulate(schema.SpawnBool("Bool"))
ts.Accumulate(schema.SpawnInt("Int"))
ts.Accumulate(schema.SpawnFloat("Float"))
ts.Accumulate(schema.SpawnString("String"))
ts.Accumulate(schema.SpawnBytes("Bytes"))
ts.Accumulate(schema.SpawnAny("Any"))
ts.Accumulate(schema.SpawnMap("Map", "String", "Any", false))
ts.Accumulate(schema.SpawnList("List", "Any", false))
// Should be &Any, really.
ts.Accumulate(schema.SpawnLink("Link"))
// TODO: schema package lacks support?
// ts.Accumulate(schema.SpawnUnit("Null", NullRepr))
}
for _, name := range node.Types.Keys {
defn := node.Types.Values[name]
// TODO: once ./schema supports anonymous/inline types, remove the ts argument.
typ, err := spawnType(ts, name, defn)
if err != nil {
return err
}
ts.Accumulate(typ)
}
// TODO: if this fails and the user forgot to check Compile's returned error,
// we can leave the TypeSystem in an unfortunate broken state:
// they can obtain types out of the TypeSystem and they are non-nil,
// but trying to use them in any way may result in panics.
// Consider making that less prone to misuse, such as making it illegal to
// call TypeByName until ValidateGraph is happy.
if errs := ts.ValidateGraph(); errs != nil {
// Return the first error.
for _, err := range errs {
return err
}
}
return nil
}
// Note that the parser and compiler support defaults. We're lacking support in bindnode.
func todoFromImplicitlyFalseBool(b *bool) bool {
if b == nil {
return false
}
return *b
}
func anonTypeName(nameOrDefn TypeNameOrInlineDefn) string {
if nameOrDefn.TypeName != nil {
return *nameOrDefn.TypeName
}
defn := *nameOrDefn.InlineDefn
switch {
case defn.TypeDefnMap != nil:
defn := defn.TypeDefnMap
return fmt.Sprintf("Map__%s__%s", defn.KeyType, anonTypeName(defn.ValueType))
case defn.TypeDefnList != nil:
defn := defn.TypeDefnList
return fmt.Sprintf("List__%s", anonTypeName(defn.ValueType))
case defn.TypeDefnLink != nil:
return anonLinkName(*defn.TypeDefnLink)
default:
panic(fmt.Errorf("%#v", defn))
}
}
func anonLinkName(defn TypeDefnLink) string {
if defn.ExpectedType != nil {
return fmt.Sprintf("Link__%s", *defn.ExpectedType)
}
return "Link__Link"
}
func parseKind(s string) datamodel.Kind {
switch s {
case "map":
return datamodel.Kind_Map
case "list":
return datamodel.Kind_List
case "null":
return datamodel.Kind_Null
case "bool":
return datamodel.Kind_Bool
case "int":
return datamodel.Kind_Int
case "float":
return datamodel.Kind_Float
case "string":
return datamodel.Kind_String
case "bytes":
return datamodel.Kind_Bytes
case "link":
return datamodel.Kind_Link
default:
return datamodel.Kind_Invalid
}
}
func spawnType(ts *schema.TypeSystem, name schema.TypeName, defn TypeDefn) (schema.Type, error) {
switch {
// Scalar types without parameters.
case defn.TypeDefnBool != nil:
return schema.SpawnBool(name), nil
case defn.TypeDefnString != nil:
return schema.SpawnString(name), nil
case defn.TypeDefnBytes != nil:
return schema.SpawnBytes(name), nil
case defn.TypeDefnInt != nil:
return schema.SpawnInt(name), nil
case defn.TypeDefnFloat != nil:
return schema.SpawnFloat(name), nil
case defn.TypeDefnList != nil:
typ := defn.TypeDefnList
tname := ""
if typ.ValueType.TypeName != nil {
tname = *typ.ValueType.TypeName
} else if tname = anonTypeName(typ.ValueType); ts.TypeByName(tname) == nil {
anonDefn := TypeDefn{
TypeDefnMap: typ.ValueType.InlineDefn.TypeDefnMap,
TypeDefnList: typ.ValueType.InlineDefn.TypeDefnList,
TypeDefnLink: typ.ValueType.InlineDefn.TypeDefnLink,
}
anonType, err := spawnType(ts, tname, anonDefn)
if err != nil {
return nil, err
}
ts.Accumulate(anonType)
}
switch {
case typ.Representation == nil ||
typ.Representation.ListRepresentation_List != nil:
// default behavior
default:
return nil, fmt.Errorf("TODO: support other list repr in schema package")
}
return schema.SpawnList(name,
tname,
todoFromImplicitlyFalseBool(typ.ValueNullable),
), nil
case defn.TypeDefnMap != nil:
typ := defn.TypeDefnMap
tname := ""
if typ.ValueType.TypeName != nil {
tname = *typ.ValueType.TypeName
} else if tname = anonTypeName(typ.ValueType); ts.TypeByName(tname) == nil {
anonDefn := TypeDefn{
TypeDefnMap: typ.ValueType.InlineDefn.TypeDefnMap,
TypeDefnList: typ.ValueType.InlineDefn.TypeDefnList,
TypeDefnLink: typ.ValueType.InlineDefn.TypeDefnLink,
}
anonType, err := spawnType(ts, tname, anonDefn)
if err != nil {
return nil, err
}
ts.Accumulate(anonType)
}
switch {
case typ.Representation == nil ||
typ.Representation.MapRepresentation_Map != nil:
// default behavior
case typ.Representation.MapRepresentation_Stringpairs != nil:
return nil, fmt.Errorf("TODO: support stringpairs map repr in schema package")
default:
return nil, fmt.Errorf("TODO: support other map repr in schema package")
}
return schema.SpawnMap(name,
typ.KeyType,
tname,
todoFromImplicitlyFalseBool(typ.ValueNullable),
), nil
case defn.TypeDefnStruct != nil:
typ := defn.TypeDefnStruct
var fields []schema.StructField
for _, fname := range typ.Fields.Keys {
field := typ.Fields.Values[fname]
tname := ""
if field.Type.TypeName != nil {
tname = *field.Type.TypeName
} else if tname = anonTypeName(field.Type); ts.TypeByName(tname) == nil {
// Note that TypeDefn and InlineDefn aren't the same enum.
anonDefn := TypeDefn{
TypeDefnMap: field.Type.InlineDefn.TypeDefnMap,
TypeDefnList: field.Type.InlineDefn.TypeDefnList,
TypeDefnLink: field.Type.InlineDefn.TypeDefnLink,
}
anonType, err := spawnType(ts, tname, anonDefn)
if err != nil {
return nil, err
}
ts.Accumulate(anonType)
}
fields = append(fields, schema.SpawnStructField(fname,
tname,
todoFromImplicitlyFalseBool(field.Optional),
todoFromImplicitlyFalseBool(field.Nullable),
))
}
var repr schema.StructRepresentation
switch {
case typ.Representation.StructRepresentation_Map != nil:
rp := typ.Representation.StructRepresentation_Map
if rp.Fields == nil {
repr = schema.SpawnStructRepresentationMap2(nil, nil)
break
}
renames := make(map[string]string, len(rp.Fields.Keys))
implicits := make(map[string]schema.ImplicitValue, len(rp.Fields.Keys))
for _, name := range rp.Fields.Keys {
details := rp.Fields.Values[name]
if details.Rename != nil {
renames[name] = *details.Rename
}
if imp := details.Implicit; imp != nil {
var sumVal schema.ImplicitValue
switch {
case imp.Bool != nil:
sumVal = schema.ImplicitValue_Bool(*imp.Bool)
case imp.String != nil:
sumVal = schema.ImplicitValue_String(*imp.String)
case imp.Int != nil:
sumVal = schema.ImplicitValue_Int(*imp.Int)
default:
panic("TODO: implicit value kind")
}
implicits[name] = sumVal
}
}
repr = schema.SpawnStructRepresentationMap2(renames, implicits)
case typ.Representation.StructRepresentation_Tuple != nil:
rp := typ.Representation.StructRepresentation_Tuple
if rp.FieldOrder == nil {
repr = schema.SpawnStructRepresentationTuple()
break
}
return nil, fmt.Errorf("TODO: support for tuples with field orders in the schema package")
case typ.Representation.StructRepresentation_Stringjoin != nil:
join := typ.Representation.StructRepresentation_Stringjoin.Join
if join == "" {
return nil, fmt.Errorf("stringjoin has empty join value")
}
repr = schema.SpawnStructRepresentationStringjoin(join)
default:
return nil, fmt.Errorf("TODO: support other struct repr in schema package")
}
return schema.SpawnStruct(name,
fields,
repr,
), nil
case defn.TypeDefnUnion != nil:
typ := defn.TypeDefnUnion
var members []schema.TypeName
for _, member := range typ.Members {
if member.TypeName != nil {
members = append(members, *member.TypeName)
} else {
tname := anonLinkName(*member.UnionMemberInlineDefn.TypeDefnLink)
members = append(members, tname)
if ts.TypeByName(tname) == nil {
anonDefn := TypeDefn{
TypeDefnLink: member.UnionMemberInlineDefn.TypeDefnLink,
}
anonType, err := spawnType(ts, tname, anonDefn)
if err != nil {
return nil, err
}
ts.Accumulate(anonType)
}
}
}
remainingMembers := make(map[string]bool)
for _, memberName := range members {
remainingMembers[memberName] = true
}
validMember := func(memberName string) error {
switch remaining, known := remainingMembers[memberName]; {
case remaining:
remainingMembers[memberName] = false
return nil
case !known:
return fmt.Errorf("%q is not a valid member of union %q", memberName, name)
default:
return fmt.Errorf("%q is duplicate in the union repr of %q", memberName, name)
}
}
var repr schema.UnionRepresentation
switch {
case typ.Representation.UnionRepresentation_Kinded != nil:
rp := typ.Representation.UnionRepresentation_Kinded
table := make(map[datamodel.Kind]schema.TypeName, len(rp.Keys))
for _, kindStr := range rp.Keys {
kind := parseKind(kindStr)
member := rp.Values[kindStr]
switch {
case member.TypeName != nil:
memberName := *member.TypeName
validMember(memberName)
table[kind] = memberName
case member.UnionMemberInlineDefn != nil:
tname := anonLinkName(*member.UnionMemberInlineDefn.TypeDefnLink)
validMember(tname)
table[kind] = tname
}
}
repr = schema.SpawnUnionRepresentationKinded(table)
case typ.Representation.UnionRepresentation_Keyed != nil:
rp := typ.Representation.UnionRepresentation_Keyed
table := make(map[string]schema.TypeName, len(rp.Keys))
for _, key := range rp.Keys {
member := rp.Values[key]
switch {
case member.TypeName != nil:
memberName := *member.TypeName
validMember(memberName)
table[key] = memberName
case member.UnionMemberInlineDefn != nil:
tname := anonLinkName(*member.UnionMemberInlineDefn.TypeDefnLink)
validMember(tname)
table[key] = tname
}
}
repr = schema.SpawnUnionRepresentationKeyed(table)
case typ.Representation.UnionRepresentation_StringPrefix != nil:
prefixes := typ.Representation.UnionRepresentation_StringPrefix.Prefixes
for _, key := range prefixes.Keys {
validMember(prefixes.Values[key])
}
repr = schema.SpawnUnionRepresentationStringprefix("", prefixes.Values)
default:
return nil, fmt.Errorf("TODO: support other union repr in schema package")
}
for memberName, remaining := range remainingMembers {
if remaining {
return nil, fmt.Errorf("%q is not present in the union repr of %q", memberName, name)
}
}
return schema.SpawnUnion(name,
members,
repr,
), nil
case defn.TypeDefnEnum != nil:
typ := defn.TypeDefnEnum
var repr schema.EnumRepresentation
// TODO: we should probably also reject duplicates.
validMember := func(name string) bool {
for _, memberName := range typ.Members {
if memberName == name {
return true
}
}
return false
}
switch {
case typ.Representation.EnumRepresentation_String != nil:
rp := typ.Representation.EnumRepresentation_String
for memberName := range rp.Values {
if !validMember(memberName) {
return nil, fmt.Errorf("%q is not a valid member of enum %q", memberName, name)
}
}
repr = schema.EnumRepresentation_String(rp.Values)
case typ.Representation.EnumRepresentation_Int != nil:
rp := typ.Representation.EnumRepresentation_Int
for memberName := range rp.Values {
if !validMember(memberName) {
return nil, fmt.Errorf("%q is not a valid member of enum %q", memberName, name)
}
}
repr = schema.EnumRepresentation_Int(rp.Values)
default:
return nil, fmt.Errorf("TODO: support other enum repr in schema package")
}
return schema.SpawnEnum(name,
typ.Members,
repr,
), nil
case defn.TypeDefnLink != nil:
typ := defn.TypeDefnLink
if typ.ExpectedType == nil {
return schema.SpawnLink(name), nil
}
return schema.SpawnLinkReference(name, *typ.ExpectedType), nil
case defn.TypeDefnAny != nil:
return schema.SpawnAny(name), nil
default:
panic(fmt.Errorf("%#v", defn))
}
}

30
vendor/github.com/ipld/go-ipld-prime/schema/dmt/doc.go generated vendored Normal file
View File

@@ -0,0 +1,30 @@
/*
Package schema/dmt contains types and functions for dealing with the data model form of IPLD Schemas.
(DMT is short for "data model tree" -- see https://ipld.io/glossary/#dmt .)
As with anything that's IPLD data model, this data can be serialized or deserialized into a wide variety of codecs.
To contrast this package with some of its neighbors and with some various formats for the data this package describes:
Schemas also have a DSL (a domain-specific language -- something that's meant to look nice, and be easy for humans to read and write),
which are parsed by the `schema/dsl` package, and produce a DMT form (defined by and handled by this package).
Schemas also have a compiled form, which is the in-memory structure that this library uses when working with them;
this compiled form differs from the DMT because it can use pointers (and that includes cyclic pointers, which is something the DMT form cannot contain).
We use the DMT form (this package) to produce the compiled form (which is the `schema` package).
Creating a Compiled schema either flows from DSL(text)->`schema/dsl`->`schema/dmt`->`schema`,
or just (some codec, e.g. JSON or CBOR or etc)->`schema/dmt`->`schema`.
The `dmt.Schema` type describes the data found at the root of an IPLD Schema document.
The `Compile` function turns such data into a `schema.TypeSystem` that is ready to be used.
The `dmt.Prototype.Schema` value is a NodePrototype that can be used to handle IPLD Schemas in DMT form as regular IPLD Nodes.
Typically this package is imported aliased as "schemadmt",
since "dmt" is a fairly generic term in the IPLD ecosystem
(see https://ipld.io/glossary/#dmt ).
Many types in this package lack documentation directly on the type;
generally, these are structs that match the IPLD schema-schema,
and so you can find descriptions of them in documentation for the schema-schema.
*/
package schemadmt

View File

@@ -0,0 +1,27 @@
package schemadmt
import (
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/bindnode"
)
// ConcatenateSchemas returns a new schema DMT object containing the
// type declarations from both.
//
// As is usual for DMT form data, there is no check about the validity
// of the result yet; you'll need to apply `Compile` on the produced value
// to produce a usable compiled typesystem or to become certain that
// all references in the DMT are satisfied, etc.
func ConcatenateSchemas(a, b *Schema) *Schema {
// The joy of having an intermediate form that's just regular data model:
// we can implement this by simply using data model "copy" operations,
// and the result is correct.
nb := Prototypes.Schema.NewBuilder()
if err := datamodel.Copy(bindnode.Wrap(a, Prototypes.Schema.Type()), nb); err != nil {
panic(err)
}
if err := datamodel.Copy(bindnode.Wrap(b, Prototypes.Schema.Type()), nb); err != nil {
panic(err)
}
return bindnode.Unwrap(nb.Build()).(*Schema)
}

View File

@@ -0,0 +1,452 @@
package schemadmt
import (
"fmt"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/bindnode"
"github.com/ipld/go-ipld-prime/schema"
)
// Prototypes contains some schema.TypedPrototype values which match
// the IPLD schema-schema -- that is, the schema that describes IPLD schemas.
// These prototypes create an in-memory representation that is backed by
// structs in this package and bindnode.
var Prototypes struct {
Schema schema.TypedPrototype
}
//go:generate go run -tags=schemadmtgen gen.go
// TypeSystem is a compiled equivalent of the IPLD schema-schema -- that is, the schema that describes IPLD schemas.
//
// The IPLD schema-schema can be found at https://ipld.io/specs/schemas/schema-schema.ipldsch .
var TypeSystem schema.TypeSystem
// In this init function, we manually create a type system that *matches* the IPLD schema-schema.
// This manual work is unfortunate, and also must be kept in-sync manually,
// but is important because breaks a cyclic dependency --
// we use the compiled schema-schema produced by this to parse other schema documents.
// We would also use it to parse... the IPLD schema-schema... if that weren't a cyclic dependency.
func init() {
var ts schema.TypeSystem
ts.Init()
// I've elided all references to Advancedlayouts stuff for the moment.
// (Not because it's particularly hard or problematic; I just want to draw a slightly smaller circle first.)
// Prelude
ts.Accumulate(schema.SpawnString("String"))
ts.Accumulate(schema.SpawnBool("Bool"))
ts.Accumulate(schema.SpawnInt("Int"))
ts.Accumulate(schema.SpawnFloat("Float"))
ts.Accumulate(schema.SpawnBytes("Bytes"))
// Schema-schema!
// In the same order as the spec's ipldsch file.
// Note that ADL stuff is excluded for now, as per above.
ts.Accumulate(schema.SpawnString("TypeName"))
ts.Accumulate(schema.SpawnStruct("Schema",
[]schema.StructField{
schema.SpawnStructField("types", "Map__TypeName__TypeDefn", false, false),
// also: `advanced AdvancedDataLayoutMap`, but as commented above, we'll pursue this later.
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnMap("Map__TypeName__TypeDefn",
"TypeName", "TypeDefn", false,
))
ts.Accumulate(schema.SpawnUnion("TypeDefn",
[]schema.TypeName{
"TypeDefnBool",
"TypeDefnString",
"TypeDefnBytes",
"TypeDefnInt",
"TypeDefnFloat",
"TypeDefnMap",
"TypeDefnList",
"TypeDefnLink",
"TypeDefnUnion",
"TypeDefnStruct",
"TypeDefnEnum",
"TypeDefnUnit",
"TypeDefnAny",
"TypeDefnCopy",
},
// TODO: spec uses inline repr.
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"bool": "TypeDefnBool",
"string": "TypeDefnString",
"bytes": "TypeDefnBytes",
"int": "TypeDefnInt",
"float": "TypeDefnFloat",
"map": "TypeDefnMap",
"list": "TypeDefnList",
"link": "TypeDefnLink",
"union": "TypeDefnUnion",
"struct": "TypeDefnStruct",
"enum": "TypeDefnEnum",
"unit": "TypeDefnUnit",
"any": "TypeDefnAny",
"copy": "TypeDefnCopy",
}),
))
ts.Accumulate(schema.SpawnUnion("TypeNameOrInlineDefn",
[]schema.TypeName{
"TypeName",
"InlineDefn",
},
schema.SpawnUnionRepresentationKinded(map[datamodel.Kind]schema.TypeName{
datamodel.Kind_String: "TypeName",
datamodel.Kind_Map: "InlineDefn",
}),
))
ts.Accumulate(schema.SpawnUnion("InlineDefn",
[]schema.TypeName{
"TypeDefnMap",
"TypeDefnList",
"TypeDefnLink",
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"map": "TypeDefnMap",
"list": "TypeDefnList",
"link": "TypeDefnLink",
}),
))
ts.Accumulate(schema.SpawnStruct("TypeDefnBool",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnString",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnBytes",
[]schema.StructField{},
// No BytesRepresentation, since we omit ADL stuff.
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnInt",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnFloat",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnMap",
[]schema.StructField{
schema.SpawnStructField("keyType", "TypeName", false, false),
schema.SpawnStructField("valueType", "TypeNameOrInlineDefn", false, false),
schema.SpawnStructField("valueNullable", "Bool", true, false), // TODO: wants to use the "implicit" feature, but not supported yet
schema.SpawnStructField("representation", "MapRepresentation", true, false), // XXXXXX
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnUnion("MapRepresentation",
[]schema.TypeName{
"MapRepresentation_Map",
"MapRepresentation_Stringpairs",
"MapRepresentation_Listpairs",
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"map": "MapRepresentation_Map",
"stringpairs": "MapRepresentation_Stringpairs",
"listpairs": "MapRepresentation_Listpairs",
}),
))
ts.Accumulate(schema.SpawnStruct("MapRepresentation_Map",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("MapRepresentation_Stringpairs",
[]schema.StructField{
schema.SpawnStructField("innerDelim", "String", false, false),
schema.SpawnStructField("entryDelim", "String", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("MapRepresentation_Listpairs",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnList",
[]schema.StructField{
schema.SpawnStructField("valueType", "TypeNameOrInlineDefn", false, false),
schema.SpawnStructField("valueNullable", "Bool", true, false), // TODO: wants to use the "implicit" feature, but not supported yet
schema.SpawnStructField("representation", "ListRepresentation", true, false), // XXXXXX
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnUnion("ListRepresentation",
[]schema.TypeName{
"ListRepresentation_List",
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"list": "ListRepresentation_List",
}),
))
ts.Accumulate(schema.SpawnStruct("ListRepresentation_List",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnUnion",
[]schema.StructField{
// n.b. we could conceivably allow TypeNameOrInlineDefn here rather than just TypeName. but... we'd rather not: imagine what that means about the type-level behavior of the union: the name munge for the anonymous type would suddenly become load-bearing. would rather not.
schema.SpawnStructField("members", "List__UnionMember", false, false),
schema.SpawnStructField("representation", "UnionRepresentation", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnList("List__UnionMember",
"UnionMember", false,
))
ts.Accumulate(schema.SpawnUnion("UnionMember",
[]schema.TypeName{
"TypeName",
"UnionMemberInlineDefn",
},
schema.SpawnUnionRepresentationKinded(map[datamodel.Kind]schema.TypeName{
datamodel.Kind_String: "TypeName",
datamodel.Kind_Map: "UnionMemberInlineDefn",
}),
))
ts.Accumulate(schema.SpawnUnion("UnionMemberInlineDefn",
[]schema.TypeName{
"TypeDefnLink",
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"link": "TypeDefnLink",
}),
))
ts.Accumulate(schema.SpawnList("List__TypeName", // todo: this is a slight hack: should be an anon inside TypeDefnUnion.members.
"TypeName", false,
))
ts.Accumulate(schema.SpawnStruct("TypeDefnLink",
[]schema.StructField{
schema.SpawnStructField("expectedType", "TypeName", true, false), // todo: this uses an implicit with a value of 'any' in the schema-schema, but that's been questioned before. maybe it should simply be an optional.
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnUnion("UnionRepresentation",
[]schema.TypeName{
"UnionRepresentation_Kinded",
"UnionRepresentation_Keyed",
"UnionRepresentation_Envelope",
"UnionRepresentation_Inline",
"UnionRepresentation_StringPrefix",
"UnionRepresentation_BytesPrefix",
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"kinded": "UnionRepresentation_Kinded",
"keyed": "UnionRepresentation_Keyed",
"envelope": "UnionRepresentation_Envelope",
"inline": "UnionRepresentation_Inline",
"stringprefix": "UnionRepresentation_StringPrefix",
"byteprefix": "UnionRepresentation_BytesPrefix",
}),
))
ts.Accumulate(schema.SpawnMap("UnionRepresentation_Kinded",
"RepresentationKind", "UnionMember", false,
))
ts.Accumulate(schema.SpawnMap("UnionRepresentation_Keyed",
"String", "UnionMember", false,
))
ts.Accumulate(schema.SpawnMap("Map__String__UnionMember",
"TypeName", "TypeDefn", false,
))
ts.Accumulate(schema.SpawnStruct("UnionRepresentation_Envelope",
[]schema.StructField{
schema.SpawnStructField("discriminantKey", "String", false, false),
schema.SpawnStructField("contentKey", "String", false, false),
schema.SpawnStructField("discriminantTable", "Map__String__UnionMember", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("UnionRepresentation_Inline",
[]schema.StructField{
schema.SpawnStructField("discriminantKey", "String", false, false),
schema.SpawnStructField("discriminantTable", "Map__String__TypeName", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("UnionRepresentation_StringPrefix",
[]schema.StructField{
schema.SpawnStructField("prefixes", "Map__String__TypeName", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("UnionRepresentation_BytesPrefix",
[]schema.StructField{
schema.SpawnStructField("prefixes", "Map__HexString__TypeName", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnMap("Map__HexString__TypeName",
"String", "TypeName", false,
))
ts.Accumulate(schema.SpawnString("HexString"))
ts.Accumulate(schema.SpawnMap("Map__String__TypeName",
"String", "TypeName", false,
))
ts.Accumulate(schema.SpawnMap("Map__TypeName__Int",
"String", "Int", false,
))
ts.Accumulate(schema.SpawnString("RepresentationKind")) // todo: RepresentationKind is supposed to be an enum, but we're puting it to a string atm.
ts.Accumulate(schema.SpawnStruct("TypeDefnStruct",
[]schema.StructField{
schema.SpawnStructField("fields", "Map__FieldName__StructField", false, false), // todo: dodging inline defn's again.
schema.SpawnStructField("representation", "StructRepresentation", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnMap("Map__FieldName__StructField",
"FieldName", "StructField", false,
))
ts.Accumulate(schema.SpawnString("FieldName"))
ts.Accumulate(schema.SpawnStruct("StructField",
[]schema.StructField{
schema.SpawnStructField("type", "TypeNameOrInlineDefn", false, false),
schema.SpawnStructField("optional", "Bool", true, false), // todo: wants to use the "implicit" feature, but not supported yet
schema.SpawnStructField("nullable", "Bool", true, false), // todo: wants to use the "implicit" feature, but not supported yet
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnUnion("StructRepresentation",
[]schema.TypeName{
"StructRepresentation_Map",
"StructRepresentation_Tuple",
"StructRepresentation_Stringpairs",
"StructRepresentation_Stringjoin",
"StructRepresentation_Listpairs",
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"map": "StructRepresentation_Map",
"tuple": "StructRepresentation_Tuple",
"stringpairs": "StructRepresentation_Stringpairs",
"stringjoin": "StructRepresentation_Stringjoin",
"listpairs": "StructRepresentation_Listpairs",
}),
))
ts.Accumulate(schema.SpawnStruct("StructRepresentation_Map",
[]schema.StructField{
schema.SpawnStructField("fields", "Map__FieldName__StructRepresentation_Map_FieldDetails", true, false), // todo: dodging inline defn's again.
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnMap("Map__FieldName__StructRepresentation_Map_FieldDetails",
"FieldName", "StructRepresentation_Map_FieldDetails", false,
))
ts.Accumulate(schema.SpawnStruct("StructRepresentation_Map_FieldDetails",
[]schema.StructField{
schema.SpawnStructField("rename", "String", true, false),
schema.SpawnStructField("implicit", "AnyScalar", true, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("StructRepresentation_Tuple",
[]schema.StructField{
schema.SpawnStructField("fieldOrder", "List__FieldName", true, false), // todo: dodging inline defn's again.
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnList("List__FieldName",
"FieldName", false,
))
ts.Accumulate(schema.SpawnStruct("StructRepresentation_Stringpairs",
[]schema.StructField{
schema.SpawnStructField("innerDelim", "String", false, false),
schema.SpawnStructField("entryDelim", "String", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("StructRepresentation_Stringjoin",
[]schema.StructField{
schema.SpawnStructField("join", "String", false, false), // review: "delim" would seem more consistent with others -- but this is currently what the schema-schema says.
schema.SpawnStructField("fieldOrder", "List__FieldName", true, false), // todo: dodging inline defn's again.
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("StructRepresentation_Listpairs",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnEnum",
[]schema.StructField{
schema.SpawnStructField("members", "List__EnumMember", false, false),
schema.SpawnStructField("representation", "EnumRepresentation", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("Unit", // todo: we should formalize the introdution of unit as first class type kind.
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnList("List__EnumMember",
"EnumMember", false,
))
ts.Accumulate(schema.SpawnString("EnumMember"))
ts.Accumulate(schema.SpawnUnion("EnumRepresentation",
[]schema.TypeName{
"EnumRepresentation_String",
"EnumRepresentation_Int",
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"string": "EnumRepresentation_String",
"int": "EnumRepresentation_Int",
}),
))
ts.Accumulate(schema.SpawnMap("EnumRepresentation_String",
"EnumMember", "String", false,
))
ts.Accumulate(schema.SpawnMap("EnumRepresentation_Int",
"EnumMember", "Int", false,
))
ts.Accumulate(schema.SpawnStruct("TypeDefnUnit",
[]schema.StructField{
schema.SpawnStructField("representation", "UnitRepresentation", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnString("UnitRepresentation")) // TODO: enum
ts.Accumulate(schema.SpawnStruct("TypeDefnAny",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnCopy",
[]schema.StructField{
schema.SpawnStructField("fromType", "TypeName", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnUnion("AnyScalar",
[]schema.TypeName{
"Bool",
"String",
"Bytes",
"Int",
"Float",
},
schema.SpawnUnionRepresentationKinded(map[datamodel.Kind]schema.TypeName{
datamodel.Kind_Bool: "Bool",
datamodel.Kind_String: "String",
datamodel.Kind_Bytes: "Bytes",
datamodel.Kind_Int: "Int",
datamodel.Kind_Float: "Float",
}),
))
if errs := ts.ValidateGraph(); errs != nil {
for _, err := range errs {
fmt.Printf("- %s\n", err)
}
panic("not happening")
}
TypeSystem = ts
Prototypes.Schema = bindnode.Prototype(
(*Schema)(nil),
TypeSystem.TypeByName("Schema"),
)
}

View File

@@ -0,0 +1,215 @@
package schemadmt
type Schema struct {
Types Map__TypeName__TypeDefn
}
type Map__TypeName__TypeDefn struct {
Keys []string
Values map[string]TypeDefn
}
type TypeDefn struct {
TypeDefnBool *TypeDefnBool
TypeDefnString *TypeDefnString
TypeDefnBytes *TypeDefnBytes
TypeDefnInt *TypeDefnInt
TypeDefnFloat *TypeDefnFloat
TypeDefnMap *TypeDefnMap
TypeDefnList *TypeDefnList
TypeDefnLink *TypeDefnLink
TypeDefnUnion *TypeDefnUnion
TypeDefnStruct *TypeDefnStruct
TypeDefnEnum *TypeDefnEnum
TypeDefnUnit *TypeDefnUnit
TypeDefnAny *TypeDefnAny
TypeDefnCopy *TypeDefnCopy
}
type TypeNameOrInlineDefn struct {
TypeName *string
InlineDefn *InlineDefn
}
type InlineDefn struct {
TypeDefnMap *TypeDefnMap
TypeDefnList *TypeDefnList
TypeDefnLink *TypeDefnLink
}
type TypeDefnBool struct {
}
type TypeDefnString struct {
}
type TypeDefnBytes struct {
}
type TypeDefnInt struct {
}
type TypeDefnFloat struct {
}
type TypeDefnMap struct {
KeyType string
ValueType TypeNameOrInlineDefn
ValueNullable *bool
Representation *MapRepresentation
}
type MapRepresentation struct {
MapRepresentation_Map *MapRepresentation_Map
MapRepresentation_Stringpairs *MapRepresentation_Stringpairs
MapRepresentation_Listpairs *MapRepresentation_Listpairs
}
type MapRepresentation_Map struct {
}
type MapRepresentation_Stringpairs struct {
InnerDelim string
EntryDelim string
}
type MapRepresentation_Listpairs struct {
}
type TypeDefnList struct {
ValueType TypeNameOrInlineDefn
ValueNullable *bool
Representation *ListRepresentation
}
type ListRepresentation struct {
ListRepresentation_List *ListRepresentation_List
}
type ListRepresentation_List struct {
}
type TypeDefnUnion struct {
Members List__UnionMember
Representation UnionRepresentation
}
type List__UnionMember []UnionMember
type UnionMember struct {
TypeName *string
UnionMemberInlineDefn *UnionMemberInlineDefn
}
type UnionMemberInlineDefn struct {
TypeDefnLink *TypeDefnLink
}
type List__TypeName []string
type TypeDefnLink struct {
ExpectedType *string
}
type UnionRepresentation struct {
UnionRepresentation_Kinded *UnionRepresentation_Kinded
UnionRepresentation_Keyed *UnionRepresentation_Keyed
UnionRepresentation_Envelope *UnionRepresentation_Envelope
UnionRepresentation_Inline *UnionRepresentation_Inline
UnionRepresentation_StringPrefix *UnionRepresentation_StringPrefix
UnionRepresentation_BytesPrefix *UnionRepresentation_BytesPrefix
}
type UnionRepresentation_Kinded struct {
Keys []string
Values map[string]UnionMember
}
type UnionRepresentation_Keyed struct {
Keys []string
Values map[string]UnionMember
}
type Map__String__UnionMember struct {
Keys []string
Values map[string]TypeDefn
}
type UnionRepresentation_Envelope struct {
DiscriminantKey string
ContentKey string
DiscriminantTable Map__String__UnionMember
}
type UnionRepresentation_Inline struct {
DiscriminantKey string
DiscriminantTable Map__String__TypeName
}
type UnionRepresentation_StringPrefix struct {
Prefixes Map__String__TypeName
}
type UnionRepresentation_BytesPrefix struct {
Prefixes Map__HexString__TypeName
}
type Map__HexString__TypeName struct {
Keys []string
Values map[string]string
}
type Map__String__TypeName struct {
Keys []string
Values map[string]string
}
type Map__TypeName__Int struct {
Keys []string
Values map[string]int
}
type TypeDefnStruct struct {
Fields Map__FieldName__StructField
Representation StructRepresentation
}
type Map__FieldName__StructField struct {
Keys []string
Values map[string]StructField
}
type StructField struct {
Type TypeNameOrInlineDefn
Optional *bool
Nullable *bool
}
type StructRepresentation struct {
StructRepresentation_Map *StructRepresentation_Map
StructRepresentation_Tuple *StructRepresentation_Tuple
StructRepresentation_Stringpairs *StructRepresentation_Stringpairs
StructRepresentation_Stringjoin *StructRepresentation_Stringjoin
StructRepresentation_Listpairs *StructRepresentation_Listpairs
}
type StructRepresentation_Map struct {
Fields *Map__FieldName__StructRepresentation_Map_FieldDetails
}
type Map__FieldName__StructRepresentation_Map_FieldDetails struct {
Keys []string
Values map[string]StructRepresentation_Map_FieldDetails
}
type StructRepresentation_Map_FieldDetails struct {
Rename *string
Implicit *AnyScalar
}
type StructRepresentation_Tuple struct {
FieldOrder *List__FieldName
}
type List__FieldName []string
type StructRepresentation_Stringpairs struct {
InnerDelim string
EntryDelim string
}
type StructRepresentation_Stringjoin struct {
Join string
FieldOrder *List__FieldName
}
type StructRepresentation_Listpairs struct {
}
type TypeDefnEnum struct {
Members List__EnumMember
Representation EnumRepresentation
}
type Unit struct {
}
type List__EnumMember []string
type EnumRepresentation struct {
EnumRepresentation_String *EnumRepresentation_String
EnumRepresentation_Int *EnumRepresentation_Int
}
type EnumRepresentation_String struct {
Keys []string
Values map[string]string
}
type EnumRepresentation_Int struct {
Keys []string
Values map[string]int
}
type TypeDefnUnit struct {
Representation string
}
type TypeDefnAny struct {
}
type TypeDefnCopy struct {
FromType string
}
type AnyScalar struct {
Bool *bool
String *string
Bytes *[]uint8
Int *int
Float *float64
}

View File

@@ -0,0 +1,734 @@
package schemadsl
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"reflect"
"strconv"
"strings"
dmt "github.com/ipld/go-ipld-prime/schema/dmt"
)
var globalTrue = true
// TODO: fuzz testing
func ParseBytes(src []byte) (*dmt.Schema, error) {
return Parse("", bytes.NewReader(src))
}
func ParseFile(path string) (*dmt.Schema, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return Parse(path, f)
}
func Parse(name string, r io.Reader) (*dmt.Schema, error) {
p := &parser{
path: name,
br: bufio.NewReader(r),
line: 1,
col: 1,
}
sch := &dmt.Schema{}
sch.Types.Values = make(map[string]dmt.TypeDefn)
for {
tok, err := p.consumeToken()
if err == io.EOF {
break
}
switch tok {
case "type":
name, err := p.consumeName()
if err != nil {
return nil, err
}
defn, err := p.typeDefn()
if err != nil {
return nil, err
}
mapAppend(&sch.Types, name, defn)
case "advanced":
return nil, p.errf("TODO: advanced")
default:
return nil, p.errf("unexpected token: %q", tok)
}
}
return sch, nil
}
func mapAppend(mapPtr, k, v interface{}) {
// TODO: delete with generics
// TODO: error on dupes
mval := reflect.ValueOf(mapPtr).Elem()
kval := reflect.ValueOf(k)
vval := reflect.ValueOf(v)
keys := mval.FieldByName("Keys")
keys.Set(reflect.Append(keys, kval))
values := mval.FieldByName("Values")
if values.IsNil() {
values.Set(reflect.MakeMap(values.Type()))
}
values.SetMapIndex(kval, vval)
}
type parser struct {
path string
br *bufio.Reader
peekedToken string
line, col int
}
func (p *parser) forwardError(err error) error {
var prefix string
if p.path != "" {
prefix = p.path + ":"
}
return fmt.Errorf("%s%d:%d: %s", prefix, p.line, p.col, err)
}
func (p *parser) errf(format string, args ...interface{}) error {
return p.forwardError(fmt.Errorf(format, args...))
}
func (p *parser) consumeToken() (string, error) {
if tok := p.peekedToken; tok != "" {
p.peekedToken = ""
return tok, nil
}
for {
// TODO: use runes for better unicode support
b, err := p.br.ReadByte()
if err == io.EOF {
return "", err // TODO: ErrUnexpectedEOF?
}
if err != nil {
return "", p.forwardError(err)
}
p.col++
switch b {
case ' ', '\t', '\r': // skip whitespace
continue
case '\n': // skip newline
// TODO: should we require a newline after each type def, struct field, etc?
p.line++
p.col = 1
continue
case '"': // quoted string
quoted, err := p.br.ReadString('"')
if err != nil {
return "", p.forwardError(err)
}
return "\"" + quoted, nil
case '{', '}', '[', ']', '(', ')', ':', '&': // simple token
return string(b), nil
case '#': // comment
_, err := p.br.ReadString('\n')
if err != nil {
return "", p.forwardError(err)
}
// tokenize the newline
if err := p.br.UnreadByte(); err != nil {
panic(err) // should never happen
}
continue
default: // string token or name
var sb strings.Builder
sb.WriteByte(b)
for {
b, err := p.br.ReadByte()
if err == io.EOF {
// Token ends at the end of the whole input.
return sb.String(), nil
}
if err != nil {
return "", p.forwardError(err)
}
// TODO: should probably allow unicode letters and numbers, like Go?
switch {
case b >= 'a' && b <= 'z', b >= 'A' && b <= 'Z':
case b >= '0' && b <= '9':
case b == '_':
default:
if err := p.br.UnreadByte(); err != nil {
panic(err) // should never happen
}
return sb.String(), nil
}
sb.WriteByte(b)
}
}
}
}
func (p *parser) consumePeeked() {
if p.peekedToken == "" {
panic("consumePeeked requires a peeked token to be present")
}
p.peekedToken = ""
}
func (p *parser) peekToken() (string, error) {
if tok := p.peekedToken; tok != "" {
return tok, nil
}
tok, err := p.consumeToken()
if err != nil {
if err == io.EOF {
// peekToken is often used when a token is optional.
// If we hit io.EOF, that's not an error.
// TODO: consider making peekToken just not return an error?
return "", nil
}
return "", err
}
p.peekedToken = tok
return tok, nil
}
func (p *parser) consumeName() (string, error) {
tok, err := p.consumeToken()
if err != nil {
return "", err
}
switch tok {
case "\"", "{", "}", "[", "]", "(", ")", ":":
return "", p.errf("expected a name, got %q", tok)
}
if tok[0] == '"' {
return "", p.errf("expected a name, got string %s", tok)
}
return tok, nil
}
func (p *parser) consumeString() (string, error) {
tok, err := p.consumeToken()
if err != nil {
return "", err
}
if tok[0] != '"' {
return "", p.errf("expected a string, got %q", tok)
}
// Unquote, too.
return tok[1 : len(tok)-1], nil
}
func (p *parser) consumeStringMap() (map[string]string, error) {
result := map[string]string{}
loop:
for {
tok, err := p.peekToken()
if err != nil {
return result, err
}
switch tok {
case "{":
p.consumePeeked()
case "}":
p.consumePeeked()
break loop
default:
key, err := p.consumeName()
if err != nil {
return result, err
}
value, err := p.consumeString()
if err != nil {
return result, err
}
result[key] = value
}
}
return result, nil
}
func (p *parser) consumeRequired(tok string) error {
got, err := p.consumeToken()
if err != nil {
return err
}
if got != tok {
return p.errf("expected %q, got %q", tok, got)
}
return nil
}
func (p *parser) typeDefn() (dmt.TypeDefn, error) {
var defn dmt.TypeDefn
kind, err := p.consumeToken()
if err != nil {
return defn, err
}
switch kind {
case "struct":
if err := p.consumeRequired("{"); err != nil {
return defn, err
}
defn.TypeDefnStruct, err = p.typeStruct()
case "union":
if err := p.consumeRequired("{"); err != nil {
return defn, err
}
defn.TypeDefnUnion, err = p.typeUnion()
case "enum":
if err := p.consumeRequired("{"); err != nil {
return defn, err
}
defn.TypeDefnEnum, err = p.typeEnum()
case "bool":
defn.TypeDefnBool = &dmt.TypeDefnBool{}
case "bytes":
defn.TypeDefnBytes = &dmt.TypeDefnBytes{}
case "float":
defn.TypeDefnFloat = &dmt.TypeDefnFloat{}
case "int":
defn.TypeDefnInt = &dmt.TypeDefnInt{}
case "link":
defn.TypeDefnLink = &dmt.TypeDefnLink{}
case "any":
defn.TypeDefnAny = &dmt.TypeDefnAny{}
case "&":
target, err := p.consumeName()
if err != nil {
return defn, err
}
defn.TypeDefnLink = &dmt.TypeDefnLink{ExpectedType: &target}
case "string":
defn.TypeDefnString = &dmt.TypeDefnString{}
case "{":
defn.TypeDefnMap, err = p.typeMap()
case "[":
defn.TypeDefnList, err = p.typeList()
case "=":
from, err := p.consumeName()
if err != nil {
return defn, err
}
defn.TypeDefnCopy = &dmt.TypeDefnCopy{FromType: from}
default:
err = p.errf("unknown type keyword: %q", kind)
}
return defn, err
}
func (p *parser) typeStruct() (*dmt.TypeDefnStruct, error) {
repr := &dmt.StructRepresentation_Map{}
repr.Fields = &dmt.Map__FieldName__StructRepresentation_Map_FieldDetails{}
defn := &dmt.TypeDefnStruct{}
for {
tok, err := p.consumeToken()
if err != nil {
return nil, err
}
if tok == "}" {
break
}
name := tok
var field dmt.StructField
loop:
for {
tok, err := p.peekToken()
if err != nil {
return nil, err
}
switch tok {
case "optional":
if field.Optional != nil {
return nil, p.errf("multiple optional keywords")
}
field.Optional = &globalTrue
p.consumePeeked()
case "nullable":
if field.Nullable != nil {
return nil, p.errf("multiple nullable keywords")
}
field.Nullable = &globalTrue
p.consumePeeked()
default:
var err error
field.Type, err = p.typeNameOrInlineDefn()
if err != nil {
return nil, err
}
break loop
}
}
tok, err = p.peekToken()
if err != nil {
return nil, err
}
if tok == "(" {
details := dmt.StructRepresentation_Map_FieldDetails{}
p.consumePeeked()
parenLoop:
for {
tok, err = p.consumeToken()
if err != nil {
return nil, err
}
switch tok {
case ")":
break parenLoop
case "rename":
str, err := p.consumeString()
if err != nil {
return nil, err
}
details.Rename = &str
case "implicit":
scalar, err := p.consumeToken()
if err != nil {
return nil, err
}
var anyScalar dmt.AnyScalar
switch {
case scalar[0] == '"': // string
s, err := strconv.Unquote(scalar)
if err != nil {
return nil, p.forwardError(err)
}
anyScalar.String = &s
case scalar == "true", scalar == "false": // bool
t := scalar == "true"
anyScalar.Bool = &t
case scalar[0] >= '0' && scalar[0] <= '0':
n, err := strconv.Atoi(scalar)
if err != nil {
return nil, p.forwardError(err)
}
anyScalar.Int = &n
default:
return nil, p.errf("unsupported implicit scalar: %s", scalar)
}
details.Implicit = &anyScalar
}
}
mapAppend(repr.Fields, name, details)
}
mapAppend(&defn.Fields, name, field)
}
reprName := "map" // default repr
if tok, err := p.peekToken(); err == nil && tok == "representation" {
p.consumePeeked()
name, err := p.consumeName()
if err != nil {
return nil, err
}
reprName = name
}
if reprName != "map" && len(repr.Fields.Keys) > 0 {
return nil, p.errf("rename and implicit are only supported for struct map representations")
}
switch reprName {
case "map":
if len(repr.Fields.Keys) == 0 {
// Fields is optional; omit it if empty.
repr.Fields = nil
}
defn.Representation.StructRepresentation_Map = repr
return defn, nil
case "tuple":
defn.Representation.StructRepresentation_Tuple = &dmt.StructRepresentation_Tuple{}
return defn, nil
// TODO: support custom fieldorder
case "stringjoin":
optMap, err := p.consumeStringMap()
if err != nil {
return nil, err
}
join, hasJoin := optMap["join"]
if !hasJoin {
return nil, p.errf("no join value provided for stringjoin repr")
}
defn.Representation.StructRepresentation_Stringjoin = &dmt.StructRepresentation_Stringjoin{
Join: join,
}
return defn, nil
default:
return nil, p.errf("unknown struct repr: %q", reprName)
}
}
func (p *parser) typeNameOrInlineDefn() (dmt.TypeNameOrInlineDefn, error) {
var typ dmt.TypeNameOrInlineDefn
tok, err := p.consumeToken()
if err != nil {
return typ, err
}
switch tok {
case "&":
expectedName, err := p.consumeName()
if err != nil {
return typ, err
}
typ.InlineDefn = &dmt.InlineDefn{TypeDefnLink: &dmt.TypeDefnLink{ExpectedType: &expectedName}}
case "[":
tlist, err := p.typeList()
if err != nil {
return typ, err
}
typ.InlineDefn = &dmt.InlineDefn{TypeDefnList: tlist}
case "{":
tmap, err := p.typeMap()
if err != nil {
return typ, err
}
typ.InlineDefn = &dmt.InlineDefn{TypeDefnMap: tmap}
default:
typ.TypeName = &tok
}
return typ, nil
}
func (p *parser) typeList() (*dmt.TypeDefnList, error) {
defn := &dmt.TypeDefnList{}
tok, err := p.peekToken()
if err != nil {
return nil, err
}
if tok == "nullable" {
defn.ValueNullable = &globalTrue
p.consumePeeked()
}
defn.ValueType, err = p.typeNameOrInlineDefn()
if err != nil {
return nil, err
}
if err := p.consumeRequired("]"); err != nil {
return defn, err
}
// TODO: repr
return defn, nil
}
func (p *parser) typeMap() (*dmt.TypeDefnMap, error) {
defn := &dmt.TypeDefnMap{}
var err error
defn.KeyType, err = p.consumeName()
if err != nil {
return nil, err
}
if err := p.consumeRequired(":"); err != nil {
return defn, err
}
tok, err := p.peekToken()
if err != nil {
return nil, err
}
if tok == "nullable" {
defn.ValueNullable = &globalTrue
p.consumePeeked()
}
defn.ValueType, err = p.typeNameOrInlineDefn()
if err != nil {
return nil, err
}
if err := p.consumeRequired("}"); err != nil {
return defn, err
}
return defn, nil
}
func (p *parser) typeUnion() (*dmt.TypeDefnUnion, error) {
defn := &dmt.TypeDefnUnion{}
var reprKeys []string
for {
tok, err := p.consumeToken()
if err != nil {
return nil, err
}
if tok == "}" {
break
}
if tok != "|" {
return nil, p.errf("expected %q or %q, got %q", "}", "|", tok)
}
var member dmt.UnionMember
nameOrInline, err := p.typeNameOrInlineDefn()
if err != nil {
return nil, err
}
if nameOrInline.TypeName != nil {
member.TypeName = nameOrInline.TypeName
} else {
if nameOrInline.InlineDefn.TypeDefnLink != nil {
member.UnionMemberInlineDefn = &dmt.UnionMemberInlineDefn{TypeDefnLink: nameOrInline.InlineDefn.TypeDefnLink}
} else {
return nil, p.errf("expected a name or inline link, got neither")
}
}
defn.Members = append(defn.Members, member)
key, err := p.consumeToken()
if err != nil {
return nil, err
}
reprKeys = append(reprKeys, key)
}
if err := p.consumeRequired("representation"); err != nil {
return nil, err
}
reprName, err := p.consumeName()
if err != nil {
return nil, err
}
switch reprName {
case "keyed":
repr := &dmt.UnionRepresentation_Keyed{}
for i, keyStr := range reprKeys {
key, err := strconv.Unquote(keyStr)
if err != nil {
return nil, p.forwardError(err)
}
mapAppend(repr, key, defn.Members[i])
}
defn.Representation.UnionRepresentation_Keyed = repr
case "kinded":
repr := &dmt.UnionRepresentation_Kinded{}
// TODO: verify keys are valid kinds? enum should do it for us?
for i, key := range reprKeys {
mapAppend(repr, key, defn.Members[i])
}
defn.Representation.UnionRepresentation_Kinded = repr
case "stringprefix":
repr := &dmt.UnionRepresentation_StringPrefix{
Prefixes: dmt.Map__String__TypeName{
Values: map[string]string{},
},
}
for i, key := range reprKeys {
// unquote prefix string
if len(key) < 2 || key[0] != '"' || key[len(key)-1] != '"' {
return nil, p.errf("invalid stringprefix %q", key)
}
key = key[1 : len(key)-1]
// add prefix to prefixes map
repr.Prefixes.Keys = append(repr.Prefixes.Keys, key)
repr.Prefixes.Values[key] = *defn.Members[i].TypeName
}
defn.Representation.UnionRepresentation_StringPrefix = repr
default:
return nil, p.errf("TODO: union repr %q", reprName)
}
return defn, nil
}
func (p *parser) typeEnum() (*dmt.TypeDefnEnum, error) {
defn := &dmt.TypeDefnEnum{}
var reprKeys []string
for {
tok, err := p.consumeToken()
if err != nil {
return nil, err
}
if tok == "}" {
break
}
if tok != "|" {
return nil, p.errf("expected %q or %q, got %q", "}", "|", tok)
}
name, err := p.consumeToken()
if err != nil {
return nil, err
}
defn.Members = append(defn.Members, name)
if tok, err := p.peekToken(); err == nil && tok == "(" {
p.consumePeeked()
key, err := p.consumeToken()
if err != nil {
return nil, err
}
reprKeys = append(reprKeys, key)
if err := p.consumeRequired(")"); err != nil {
return defn, err
}
} else {
reprKeys = append(reprKeys, "")
}
}
reprName := "string" // default repr
if tok, err := p.peekToken(); err == nil && tok == "representation" {
p.consumePeeked()
name, err := p.consumeName()
if err != nil {
return nil, err
}
reprName = name
}
switch reprName {
case "string":
repr := &dmt.EnumRepresentation_String{}
for i, key := range reprKeys {
if key == "" {
continue // no key; defaults to the name
}
if key[0] != '"' {
return nil, p.errf("enum string representation used with non-string key: %s", key)
}
unquoted, err := strconv.Unquote(key)
if err != nil {
return nil, p.forwardError(err)
}
mapAppend(repr, defn.Members[i], unquoted)
}
defn.Representation.EnumRepresentation_String = repr
case "int":
repr := &dmt.EnumRepresentation_Int{}
for i, key := range reprKeys {
if key[0] != '"' {
return nil, p.errf("enum int representation used with non-string key: %s", key)
}
unquoted, err := strconv.Unquote(key)
if err != nil {
return nil, p.forwardError(err)
}
parsed, err := strconv.Atoi(unquoted)
if err != nil {
return nil, p.forwardError(err)
}
mapAppend(repr, defn.Members[i], parsed)
}
defn.Representation.EnumRepresentation_Int = repr
default:
return nil, p.errf("unknown enum repr: %q", reprName)
}
return defn, nil
}

163
vendor/github.com/ipld/go-ipld-prime/schema/errors.go generated vendored Normal file
View File

@@ -0,0 +1,163 @@
package schema
import (
"fmt"
"strings"
"github.com/ipld/go-ipld-prime/datamodel"
)
// TODO: errors in this package remain somewhat slapdash.
//
// - datamodel.ErrUnmatchable is used as a catch-all in some places, and contains who-knows-what values wrapped in the Reason field.
// - sometimes this wraps things like strconv errors... and on the one hand, i'm kinda okay with that; on the other, maybe saying a bit more with types before getting to that kind of shrug would be nice.
// - we probably want to use `Type` values, right?
// - or do we: because then we probably need a `Repr bool` next to it, or lots of messages would be nonsensical.
// - this is *currently* problematic because we don't actually generate type info consts yet. Hopefully soon; but the pain, meanwhile, is... substantial.
// - "substantial" is an understatement. it makes incremental development almost impossible because stringifying error reports turn into nil pointer crashes!
// - other ipld-wide errors like `datamodel.ErrWrongKind` *sometimes* refer to a TypeName... but don't *have* to, because they also arise at the merely-datamodel level; what would we do with these?
// - it's undesirable (not to mention intensely forbidden for import cycle reasons) for those error types to refer to schema.Type.
// - if we must have TypeName treated stringily in some cases, is it really useful to use full type info in other cases -- inconsistently?
// - regardless of where we end up with this, some sort of an embed for helping deal with munging and printing this would probably be wise.
// - generally, whether you should expect an "datamodel.Err*" or a "schema.Err*" from various methods is quite unclear.
// - it's possible that we should wrap *all* schema-level errors in a single "datamodel.ErrSchemaNoMatch" error of some kind, to fix the above. (and maybe that's what ErrUnmatchable really is.) as yet undecided.
// ErrUnmatchable is the error raised when processing data with IPLD Schemas and
// finding data which cannot be matched into the schema.
// It will be returned by NodeAssemblers and NodeBuilders when they are fed unmatchable data.
// As a result, it will also often be seen returned from unmarshalling
// when unmarshalling into schema-constrained NodeAssemblers.
//
// ErrUnmatchable provides the name of the type in the schema that data couldn't be matched to,
// and wraps another error as the more detailed reason.
type ErrUnmatchable struct {
// TypeName will indicate the named type of a node the function was called on.
TypeName string
// Reason must always be present. ErrUnmatchable doesn't say much otherwise.
Reason error
}
func (e ErrUnmatchable) Error() string {
return fmt.Sprintf("matching data to schema of %s rejected: %s", e.TypeName, e.Reason)
}
// Reasonf returns a new ErrUnmatchable with a Reason field set to the Errorf of the arguments.
// It's a helper function for creating untyped error reasons without importing the fmt package.
func (e ErrUnmatchable) Reasonf(format string, a ...interface{}) ErrUnmatchable {
return ErrUnmatchable{e.TypeName, fmt.Errorf(format, a...)}
}
// Is provides support for Go's standard errors.Is function so that
// errors.Is(yourError, ErrUnmatchable) may be used to match the type of error.
func (e ErrUnmatchable) Is(err error) bool {
_, ok := err.(ErrUnmatchable)
return ok
}
// ErrMissingRequiredField is returned when calling 'Finish' on a NodeAssembler
// for a Struct that has not has all required fields set.
type ErrMissingRequiredField struct {
Missing []string
}
func (e ErrMissingRequiredField) Error() string {
return "missing required fields: " + strings.Join(e.Missing, ",")
}
// Is provides support for Go's standard errors.Is function so that
// errors.Is(yourError, ErrMissingRequiredField) may be used to match the type of error.
func (e ErrMissingRequiredField) Is(err error) bool {
_, ok := err.(ErrMissingRequiredField)
return ok
}
// ErrInvalidKey indicates a key is invalid for some reason.
//
// This is only possible for typed nodes; specifically, it may show up when
// handling struct types, or maps with interesting key types.
// (Other kinds of key invalidity that happen for untyped maps
// fall under ErrRepeatedMapKey or ErrWrongKind.)
// (Union types use ErrInvalidUnionDiscriminant instead of ErrInvalidKey,
// even when their representation strategy is maplike.)
type ErrInvalidKey struct {
// TypeName will indicate the named type of a node the function was called on.
TypeName string
// Key is the key that was rejected.
Key datamodel.Node
// Reason, if set, may provide details (for example, the reason a key couldn't be converted to a type).
// If absent, it'll be presumed "no such field".
// ErrUnmatchable may show up as a reason for typed maps with complex keys.
Reason error
}
func (e ErrInvalidKey) Error() string {
if e.Reason == nil {
return fmt.Sprintf("invalid key for map %s: %q: no such field", e.TypeName, e.Key)
} else {
return fmt.Sprintf("invalid key for map %s: %q: %s", e.TypeName, e.Key, e.Reason)
}
}
// Is provides support for Go's standard errors.Is function so that
// errors.Is(yourError, ErrInvalidKey) may be used to match the type of error.
func (e ErrInvalidKey) Is(err error) bool {
_, ok := err.(ErrInvalidKey)
return ok
}
// ErrNoSuchField may be returned from lookup functions on the Node
// interface when a field is requested which doesn't exist,
// or from assigning data into on a MapAssembler for a struct
// when the key doesn't match a field name in the structure
// (or, when assigning data into a ListAssembler and the list size has
// reached out of bounds, in case of a struct with list-like representations!).
type ErrNoSuchField struct {
Type Type
Field datamodel.PathSegment
}
func (e ErrNoSuchField) Error() string {
if e.Type == nil {
return fmt.Sprintf("no such field: {typeinfomissing}.%s", e.Field)
}
return fmt.Sprintf("no such field: %s.%s", e.Type.Name(), e.Field)
}
// Is provides support for Go's standard errors.Is function so that
// errors.Is(yourError, ErrNoSuchField) may be used to match the type of error.
func (e ErrNoSuchField) Is(err error) bool {
_, ok := err.(ErrNoSuchField)
return ok
}
// ErrNotUnionStructure means data was fed into a union assembler that can't match the union.
//
// This could have one of several reasons, which are explained in the detail text:
//
// - there are too many entries in the map;
// - the keys of critical entries aren't found;
// - keys are found that aren't any of the expected critical keys;
// - etc.
//
// TypeName is currently a string... see comments at the top of this file for
// remarks on the issues we need to address about these identifiers in errors in general.
type ErrNotUnionStructure struct {
TypeName string
Detail string
}
func (e ErrNotUnionStructure) Error() string {
return fmt.Sprintf("cannot match schema: union structure constraints for %s caused rejection: %s", e.TypeName, e.Detail)
}
// Is provides support for Go's standard errors.Is function so that
// errors.Is(yourError, ErrNotUnionStructure) may be used to match the type of error.
func (e ErrNotUnionStructure) Is(err error) bool {
_, ok := err.(ErrNotUnionStructure)
return ok
}

112
vendor/github.com/ipld/go-ipld-prime/schema/kind.go generated vendored Normal file
View File

@@ -0,0 +1,112 @@
package schema
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// TypeKind is an enum of kind in the IPLD Schema system.
//
// Note that schema.TypeKind is distinct from datamodel.Kind!
// Schema kinds include concepts such as "struct" and "enum", which are
// concepts only introduced by the Schema layer, and not present in the
// Data Model layer.
type TypeKind uint8
const (
TypeKind_Invalid TypeKind = 0
TypeKind_Map TypeKind = '{'
TypeKind_List TypeKind = '['
TypeKind_Unit TypeKind = '1'
TypeKind_Bool TypeKind = 'b'
TypeKind_Int TypeKind = 'i'
TypeKind_Float TypeKind = 'f'
TypeKind_String TypeKind = 's'
TypeKind_Bytes TypeKind = 'x'
TypeKind_Link TypeKind = '/'
TypeKind_Struct TypeKind = '$'
TypeKind_Union TypeKind = '^'
TypeKind_Enum TypeKind = '%'
TypeKind_Any TypeKind = '?'
)
func (k TypeKind) String() string {
switch k {
case TypeKind_Invalid:
return "invalid"
case TypeKind_Map:
return "map"
case TypeKind_Any:
return "any"
case TypeKind_List:
return "list"
case TypeKind_Unit:
return "unit"
case TypeKind_Bool:
return "bool"
case TypeKind_Int:
return "int"
case TypeKind_Float:
return "float"
case TypeKind_String:
return "string"
case TypeKind_Bytes:
return "bytes"
case TypeKind_Link:
return "link"
case TypeKind_Struct:
return "struct"
case TypeKind_Union:
return "union"
case TypeKind_Enum:
return "enum"
default:
panic("invalid enumeration value!")
}
}
// ActsLike returns a constant from the datamodel.Kind enum describing what
// this schema.TypeKind acts like at the Data Model layer.
//
// Things with similar names are generally conserved
// (e.g. "map" acts like "map");
// concepts added by the schema layer have to be mapped onto something
// (e.g. "struct" acts like "map").
//
// Note that this mapping describes how a typed Node will *act*, programmatically;
// it does not necessarily describe how it will be *serialized*
// (for example, a struct will always act like a map, even if it has a tuple
// representation strategy and thus becomes a list when serialized).
func (k TypeKind) ActsLike() datamodel.Kind {
switch k {
case TypeKind_Invalid:
return datamodel.Kind_Invalid
case TypeKind_Map:
return datamodel.Kind_Map
case TypeKind_List:
return datamodel.Kind_List
case TypeKind_Unit:
return datamodel.Kind_Bool // maps to 'true'. // REVIEW: odd that this doesn't map to 'null'? // TODO this should be standardized in the specs, in a table.
case TypeKind_Bool:
return datamodel.Kind_Bool
case TypeKind_Int:
return datamodel.Kind_Int
case TypeKind_Float:
return datamodel.Kind_Float
case TypeKind_String:
return datamodel.Kind_String
case TypeKind_Bytes:
return datamodel.Kind_Bytes
case TypeKind_Link:
return datamodel.Kind_Link
case TypeKind_Struct:
return datamodel.Kind_Map // clear enough: fields are keys.
case TypeKind_Union:
return datamodel.Kind_Map
case TypeKind_Enum:
return datamodel.Kind_String // 'AsString' is the one clear thing to define.
case TypeKind_Any:
return datamodel.Kind_Invalid // TODO: maybe ActsLike should return (Kind, bool)
default:
panic("invalid enumeration value!")
}
}

9
vendor/github.com/ipld/go-ipld-prime/schema/maybe.go generated vendored Normal file
View File

@@ -0,0 +1,9 @@
package schema
type Maybe uint8
const (
Maybe_Absent = Maybe(0)
Maybe_Null = Maybe(1)
Maybe_Value = Maybe(2)
)

View File

@@ -0,0 +1,220 @@
package schema
import (
"fmt"
"github.com/ipld/go-ipld-prime/datamodel"
)
// Everything in this file is __a temporary hack__ and will be __removed__.
//
// These methods will only hang around until more of the "ast" packages are finished;
// thereafter, building schema.Type and schema.TypeSystem values will only be
// possible through first constructing a schema AST, and *then* using Reify(),
// which will validate things correctly, cycle-check, cross-link, etc.
//
// (Meanwhile, we're using these methods in the codegen prototypes.)
// These methods use Type objects as parameters when pointing to other things,
// but this is... turning out consistently problematic.
// Even when we're doing this hacky direct-call doesn't-need-to-be-serializable temp stuff,
// as written, this doesn't actually let us express cyclic things viably!
// The same initialization questions are also going to come up again when we try to make
// concrete values in the output of codegen.
// Maybe it's actually just a bad idea to have our reified Type types use Type pointers at all.
// (I will never get tired of the tongue twisters, evidently.)
// I'm not actually using that much, and it's always avoidable (it's trivial to replace with a map lookup bouncing through a 'ts' variable somewhere).
// And having the AST gen'd types be... just... the thing... sounds nice. It could save a lot of work.
// (It would mean the golang types don't tell you whether the values have been checked for global properties or not, but, eh.)
// (It's not really compatible with "Prototype and Type are the same thing for codegen'd stuff", either (or, we need more interfaces, and to *really* lean into them), but maybe that's okay.)
func SpawnTypeSystem(types ...Type) (*TypeSystem, []error) {
ts := TypeSystem{}
ts.Init()
for _, typ := range types {
ts.Accumulate(typ)
}
if errs := ts.ValidateGraph(); errs != nil {
return nil, errs
}
return &ts, nil
}
func MustTypeSystem(types ...Type) *TypeSystem {
if ts, err := SpawnTypeSystem(types...); err != nil {
panic(err)
} else {
return ts
}
}
func SpawnString(name TypeName) *TypeString {
return &TypeString{typeBase{name, nil}}
}
func SpawnBool(name TypeName) *TypeBool {
return &TypeBool{typeBase{name, nil}}
}
func SpawnInt(name TypeName) *TypeInt {
return &TypeInt{typeBase{name, nil}}
}
func SpawnFloat(name TypeName) *TypeFloat {
return &TypeFloat{typeBase{name, nil}}
}
func SpawnBytes(name TypeName) *TypeBytes {
return &TypeBytes{typeBase{name, nil}}
}
func SpawnLink(name TypeName) *TypeLink {
return &TypeLink{typeBase{name, nil}, "", false}
}
func SpawnLinkReference(name TypeName, pointsTo TypeName) *TypeLink {
return &TypeLink{typeBase{name, nil}, pointsTo, true}
}
func SpawnList(name TypeName, valueType TypeName, nullable bool) *TypeList {
return &TypeList{typeBase{name, nil}, false, valueType, nullable}
}
func SpawnMap(name TypeName, keyType TypeName, valueType TypeName, nullable bool) *TypeMap {
return &TypeMap{typeBase{name, nil}, false, keyType, valueType, nullable}
}
func SpawnAny(name TypeName) *TypeAny {
return &TypeAny{typeBase{name, nil}}
}
func SpawnStruct(name TypeName, fields []StructField, repr StructRepresentation) *TypeStruct {
v := &TypeStruct{
typeBase{name, nil},
fields,
make(map[string]StructField, len(fields)),
repr,
}
for i := range fields {
fields[i].parent = v
v.fieldsMap[fields[i].name] = fields[i]
}
switch repr.(type) {
case StructRepresentation_Stringjoin:
for _, f := range fields {
if f.IsMaybe() {
panic("neither nullable nor optional is supported on struct stringjoin representation")
}
}
case nil:
v.representation = SpawnStructRepresentationMap(nil)
}
return v
}
func SpawnStructField(name string, typ TypeName, optional bool, nullable bool) StructField {
return StructField{nil /*populated later*/, name, typ, optional, nullable}
}
func SpawnStructRepresentationMap(renames map[string]string) StructRepresentation_Map {
return StructRepresentation_Map{renames, nil}
}
func SpawnStructRepresentationMap2(renames map[string]string, implicits map[string]ImplicitValue) StructRepresentation_Map {
return StructRepresentation_Map{renames, implicits}
}
func SpawnStructRepresentationTuple() StructRepresentation_Tuple {
return StructRepresentation_Tuple{}
}
func SpawnStructRepresentationStringjoin(delim string) StructRepresentation_Stringjoin {
return StructRepresentation_Stringjoin{delim}
}
func SpawnUnion(name TypeName, members []TypeName, repr UnionRepresentation) *TypeUnion {
return &TypeUnion{typeBase{name, nil}, members, repr}
}
func SpawnUnionRepresentationKeyed(table map[string]TypeName) UnionRepresentation_Keyed {
return UnionRepresentation_Keyed{table}
}
func SpawnUnionRepresentationKinded(table map[datamodel.Kind]TypeName) UnionRepresentation_Kinded {
return UnionRepresentation_Kinded{table}
}
func SpawnUnionRepresentationStringprefix(delim string, table map[string]TypeName) UnionRepresentation_Stringprefix {
return UnionRepresentation_Stringprefix{delim, table}
}
func SpawnEnum(name TypeName, members []string, repr EnumRepresentation) *TypeEnum {
return &TypeEnum{typeBase{name, nil}, members, repr}
}
// The methods relating to TypeSystem are also mutation-heavy and placeholdery.
func (ts *TypeSystem) Init() {
ts.namedTypes = make(map[TypeName]Type)
}
func (ts *TypeSystem) Accumulate(typ Type) {
typ._Type(ts)
name := typ.Name()
if _, ok := ts.namedTypes[name]; ok {
panic(fmt.Sprintf("duplicate type name: %s", name))
}
ts.namedTypes[name] = typ
ts.names = append(ts.names, name)
}
func (ts TypeSystem) GetTypes() map[TypeName]Type {
return ts.namedTypes
}
func (ts TypeSystem) TypeByName(n string) Type {
return ts.namedTypes[n]
}
func (ts TypeSystem) Names() []TypeName {
return ts.names
}
// ValidateGraph checks that all type names referenced are defined.
//
// It does not do any other validations of individual type's sensibleness
// (that should've happened when they were created
// (although also note many of those validates are NYI,
// and are roadmapped for after we research self-hosting)).
func (ts TypeSystem) ValidateGraph() []error {
var ee []error
for tn, t := range ts.namedTypes {
switch t2 := t.(type) {
case *TypeBool,
*TypeInt,
*TypeFloat,
*TypeString,
*TypeBytes,
*TypeEnum:
continue // nothing to check: these are leaf nodes and refer to no other types.
case *TypeLink:
if !t2.hasReferencedType {
continue
}
if _, ok := ts.namedTypes[t2.referencedType]; !ok {
ee = append(ee, fmt.Errorf("type %s refers to missing type %s (as link reference type)", tn, t2.referencedType))
}
case *TypeStruct:
for _, f := range t2.fields {
if _, ok := ts.namedTypes[f.typ]; !ok {
ee = append(ee, fmt.Errorf("type %s refers to missing type %s (in field %q)", tn, f.typ, f.name))
}
}
case *TypeMap:
if _, ok := ts.namedTypes[t2.keyType]; !ok {
ee = append(ee, fmt.Errorf("type %s refers to missing type %s (as key type)", tn, t2.keyType))
}
if _, ok := ts.namedTypes[t2.valueType]; !ok {
ee = append(ee, fmt.Errorf("type %s refers to missing type %s (as value type)", tn, t2.valueType))
}
case *TypeList:
if _, ok := ts.namedTypes[t2.valueType]; !ok {
ee = append(ee, fmt.Errorf("type %s refers to missing type %s (as value type)", tn, t2.valueType))
}
case *TypeUnion:
for _, mn := range t2.members {
if _, ok := ts.namedTypes[mn]; !ok {
ee = append(ee, fmt.Errorf("type %s refers to missing type %s (as a member)", tn, mn))
}
}
}
}
return ee
}

270
vendor/github.com/ipld/go-ipld-prime/schema/type.go generated vendored Normal file
View File

@@ -0,0 +1,270 @@
package schema
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
type TypeName = string
// typesystem.Type is an union interface; each of the `Type*` concrete types
// in this package are one of its members.
//
// Specifically,
//
// TypeBool
// TypeString
// TypeBytes
// TypeInt
// TypeFloat
// TypeMap
// TypeList
// TypeLink
// TypeUnion
// TypeStruct
// TypeEnum
//
// are all of the kinds of Type.
//
// This is a closed union; you can switch upon the above members without
// including a default case. The membership is closed by the unexported
// '_Type' method; you may use the BurntSushi/go-sumtype tool to check
// your switches for completeness.
//
// Many interesting properties of each Type are only defined for that specific
// type, so it's typical to use a type switch to handle each type of Type.
// (Your humble author is truly sorry for the word-mash that results from
// attempting to describe the types that describe the typesystem.Type.)
//
// For example, to inspect the kind of fields in a struct: you might
// cast a `Type` interface into `TypeStruct`, and then the `Fields()` on
// that `TypeStruct` can be inspected. (`Fields()` isn't defined for any
// other kind of Type.)
type Type interface {
// Unexported marker method to force the union closed.
// Also used to set the internal pointer back to the universe its part of.
_Type(*TypeSystem)
// Returns a pointer to the TypeSystem this Type is a member of.
TypeSystem() *TypeSystem
// Returns the string name of the Type. This name is unique within the
// universe this type is a member of, *unless* this type is Anonymous,
// in which case a string describing the type will still be returned, but
// that string will not be required to be unique.
Name() TypeName
// Returns the TypeKind of this Type.
//
// The returned value is a 1:1 association with which of the concrete
// "schema.Type*" structs this interface can be cast to.
//
// Note that a schema.TypeKind is a different enum than datamodel.Kind;
// and furthermore, there's no strict relationship between them.
// schema.TypedNode values can be described by *two* distinct Kinds:
// 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 Kinds can be determined statically; but not always:
// it can sometimes be necessary to inspect the value quite concretely
// (e.g., `schema.TypedNode{}.Representation().Kind()`) 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).
TypeKind() TypeKind
// RepresentationBehavior returns a description of how the representation
// of this type will behave in terms of the IPLD Data Model.
// This property varies based on the representation strategy of a type.
//
// In one case, the representation behavior cannot be known statically,
// and varies based on the data: kinded unions have this trait.
//
// This property is used by kinded unions, which require that their members
// all have distinct representation behavior.
// (It follows that a kinded union cannot have another kinded union as a member.)
//
// You may also be interested in a related property that might have been called "TypeBehavior".
// However, this method doesn't exist, because it's a deterministic property of `TypeKind()`!
// You can use `TypeKind.ActsLike()` to get type-level behavioral information.
RepresentationBehavior() datamodel.Kind
}
var (
_ Type = &TypeBool{}
_ Type = &TypeString{}
_ Type = &TypeBytes{}
_ Type = &TypeInt{}
_ Type = &TypeFloat{}
_ Type = &TypeAny{}
_ Type = &TypeMap{}
_ Type = &TypeList{}
_ Type = &TypeLink{}
_ Type = &TypeUnion{}
_ Type = &TypeStruct{}
_ Type = &TypeEnum{}
)
type typeBase struct {
name TypeName
universe *TypeSystem
}
type TypeBool struct {
typeBase
}
type TypeString struct {
typeBase
}
type TypeBytes struct {
typeBase
}
type TypeInt struct {
typeBase
}
type TypeFloat struct {
typeBase
}
type TypeAny struct {
typeBase
}
type TypeMap struct {
typeBase
anonymous bool
keyType TypeName // must be Kind==string (e.g. Type==String|Enum).
valueType TypeName
valueNullable bool
}
type TypeList struct {
typeBase
anonymous bool
valueType TypeName
valueNullable bool
}
type TypeLink struct {
typeBase
referencedType TypeName
hasReferencedType bool
// ...?
}
type TypeUnion struct {
typeBase
// Members are listed in the order they appear in the schema.
// To find the discriminant info, you must look inside the representation; they all contain a 'table' of some kind in which the member types are the values.
// Note that multiple appearances of the same type as distinct members of the union is not possible.
// While we could do this... A: that's... odd, and nearly never called for; B: not possible with kinded mode; C: imagine the golang-native type switch! it's impossible.
// We rely on this clarity in many ways: most visibly, the type-level Node implementation for a union always uses the type names as if they were map keys! This behavior is consistent for all union representations.
members []TypeName
representation UnionRepresentation
}
type UnionRepresentation interface{ _UnionRepresentation() }
func (UnionRepresentation_Keyed) _UnionRepresentation() {}
func (UnionRepresentation_Kinded) _UnionRepresentation() {}
func (UnionRepresentation_Envelope) _UnionRepresentation() {}
func (UnionRepresentation_Inline) _UnionRepresentation() {}
func (UnionRepresentation_Stringprefix) _UnionRepresentation() {}
// A bunch of these tables in union representation might be easier to use if flipped;
// we almost always index into them by type (since that's what we have an ordered list of);
// and they're unique in both directions, so it's equally valid either way.
// The order they're currently written in matches the serial form in the schema AST.
type UnionRepresentation_Keyed struct {
table map[string]TypeName // key is user-defined freetext
}
type UnionRepresentation_Kinded struct {
table map[datamodel.Kind]TypeName
}
//lint:ignore U1000 implementation TODO
type UnionRepresentation_Envelope struct {
discriminantKey string
contentKey string
table map[string]TypeName // key is user-defined freetext
}
//lint:ignore U1000 implementation TODO
type UnionRepresentation_Inline struct {
discriminantKey string
table map[string]TypeName // key is user-defined freetext
}
type UnionRepresentation_Stringprefix struct {
delim string
table map[string]TypeName // key is user-defined freetext
}
type TypeStruct struct {
typeBase
// n.b. `Fields` is an (order-preserving!) map in the schema-schema;
// but it's a list here, with the keys denormalized into the value,
// because that's typically how we use it.
fields []StructField
fieldsMap map[string]StructField // same content, indexed for lookup.
representation StructRepresentation
}
type StructField struct {
parent *TypeStruct
name string
typ TypeName
optional bool
nullable bool
}
type StructRepresentation interface{ _StructRepresentation() }
func (StructRepresentation_Map) _StructRepresentation() {}
func (StructRepresentation_Tuple) _StructRepresentation() {}
func (StructRepresentation_StringPairs) _StructRepresentation() {}
func (StructRepresentation_Stringjoin) _StructRepresentation() {}
type StructRepresentation_Map struct {
renames map[string]string
implicits map[string]ImplicitValue
}
type StructRepresentation_Tuple struct{}
//lint:ignore U1000 implementation TODO
type StructRepresentation_StringPairs struct{ sep1, sep2 string }
type StructRepresentation_Stringjoin struct{ sep string }
type TypeEnum struct {
typeBase
members []string
representation EnumRepresentation
}
type EnumRepresentation interface{ _EnumRepresentation() }
func (EnumRepresentation_String) _EnumRepresentation() {}
func (EnumRepresentation_Int) _EnumRepresentation() {}
type EnumRepresentation_String map[string]string
type EnumRepresentation_Int map[string]int
// ImplicitValue is an sum type holding values that are implicits.
// It's not an 'Any' value because it can't be recursive
// (or to be slightly more specific, it can be one of the recursive kinds,
// but if so, only its empty value is valid here).
type ImplicitValue interface{ _ImplicitValue() }
func (ImplicitValue_EmptyList) _ImplicitValue() {}
func (ImplicitValue_EmptyMap) _ImplicitValue() {}
func (ImplicitValue_String) _ImplicitValue() {}
func (ImplicitValue_Int) _ImplicitValue() {}
func (ImplicitValue_Bool) _ImplicitValue() {}
type ImplicitValue_EmptyList struct{}
type ImplicitValue_EmptyMap struct{}
type ImplicitValue_String string
type ImplicitValue_Int int
type ImplicitValue_Bool bool

View File

@@ -0,0 +1,287 @@
package schema
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
/* cookie-cutter standard interface stuff */
func (t *typeBase) _Type(ts *TypeSystem) {
t.universe = ts
}
func (t typeBase) TypeSystem() *TypeSystem { return t.universe }
func (t typeBase) Name() TypeName { return t.name }
func (TypeBool) TypeKind() TypeKind { return TypeKind_Bool }
func (TypeString) TypeKind() TypeKind { return TypeKind_String }
func (TypeBytes) TypeKind() TypeKind { return TypeKind_Bytes }
func (TypeInt) TypeKind() TypeKind { return TypeKind_Int }
func (TypeFloat) TypeKind() TypeKind { return TypeKind_Float }
func (TypeAny) TypeKind() TypeKind { return TypeKind_Any }
func (TypeMap) TypeKind() TypeKind { return TypeKind_Map }
func (TypeList) TypeKind() TypeKind { return TypeKind_List }
func (TypeLink) TypeKind() TypeKind { return TypeKind_Link }
func (TypeUnion) TypeKind() TypeKind { return TypeKind_Union }
func (TypeStruct) TypeKind() TypeKind { return TypeKind_Struct }
func (TypeEnum) TypeKind() TypeKind { return TypeKind_Enum }
func (TypeBool) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_Bool }
func (TypeString) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_String }
func (TypeBytes) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_Bytes }
func (TypeInt) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_Int }
func (TypeFloat) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_Float }
func (TypeMap) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_Map }
func (TypeList) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_List }
func (TypeLink) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_Link }
func (t TypeUnion) RepresentationBehavior() datamodel.Kind {
switch t.representation.(type) {
case UnionRepresentation_Keyed:
return datamodel.Kind_Map
case UnionRepresentation_Kinded:
return datamodel.Kind_Invalid // you can't know with this one, until you see the value (and thus can its inhabitant's behavior)!
case UnionRepresentation_Envelope:
return datamodel.Kind_Map
case UnionRepresentation_Inline:
return datamodel.Kind_Map
case UnionRepresentation_Stringprefix:
return datamodel.Kind_String
default:
panic("unreachable")
}
}
func (t TypeStruct) RepresentationBehavior() datamodel.Kind {
switch t.representation.(type) {
case StructRepresentation_Map:
return datamodel.Kind_Map
case StructRepresentation_Tuple:
return datamodel.Kind_List
case StructRepresentation_StringPairs:
return datamodel.Kind_String
case StructRepresentation_Stringjoin:
return datamodel.Kind_String
default:
panic("unreachable")
}
}
func (t TypeEnum) RepresentationBehavior() datamodel.Kind {
// TODO: this should have a representation strategy switch too; sometimes that will indicate int representation behavior.
return datamodel.Kind_String
}
func (t TypeAny) RepresentationBehavior() datamodel.Kind {
return datamodel.Kind_Invalid // TODO: what can we possibly do here?
}
/* interesting methods per Type type */
// beware: many of these methods will change when we successfully bootstrap self-hosting.
//
// The current methods return reified Type objects; in the future, there might be less of that.
// Returning reified Type objects requires bouncing lookups through the typesystem map;
// this is unavoidable because we need to handle cycles in definitions.
// However, the extra (and cyclic) pointers that requires won't necessarily jive well if
// we remake the Type types to have close resemblances to the Data Model tree data.)
//
// It's also unfortunate that some of the current methods collide in name with
// the names of the Data Model fields. We might reshuffling things to reduce this.
//
// At any rate, all of these changes will come as a sweep once we
// get a self-hosting gen of the schema-schema, not before
// (the effort of updating template references is substantial).
// IsAnonymous is returns true if the type was unnamed. Unnamed types will
// claim to have a Name property like `{Foo:Bar}`, and this is not guaranteed
// to be a unique string for all types in the universe.
func (t TypeMap) IsAnonymous() bool {
return t.anonymous
}
// KeyType returns the Type of the map keys.
//
// Note that map keys will must always be some type which is representable as a
// string in the IPLD Data Model (e.g. either TypeString or TypeEnum).
func (t TypeMap) KeyType() Type {
return t.universe.namedTypes[t.keyType]
}
// ValueType returns the Type of the map values.
func (t TypeMap) ValueType() Type {
return t.universe.namedTypes[t.valueType]
}
// ValueIsNullable returns a bool describing if the map values are permitted
// to be null.
func (t TypeMap) ValueIsNullable() bool {
return t.valueNullable
}
// IsAnonymous is returns true if the type was unnamed. Unnamed types will
// claim to have a Name property like `[Foo]`, and this is not guaranteed
// to be a unique string for all types in the universe.
func (t TypeList) IsAnonymous() bool {
return t.anonymous
}
// ValueType returns to the Type of the list values.
func (t TypeList) ValueType() Type {
return t.universe.namedTypes[t.valueType]
}
// ValueIsNullable returns a bool describing if the list values are permitted
// to be null.
func (t TypeList) ValueIsNullable() bool {
return t.valueNullable
}
// Members returns the list of all types that are possible inhabitants of this union.
func (t TypeUnion) Members() []Type {
a := make([]Type, len(t.members))
for i := range t.members {
a[i] = t.universe.namedTypes[t.members[i]]
}
return a
}
func (t TypeUnion) RepresentationStrategy() UnionRepresentation {
return t.representation
}
func (r UnionRepresentation_Keyed) GetDiscriminant(t Type) string {
for d, t2 := range r.table {
if t2 == t.Name() {
return d
}
}
panic("that type isn't a member of this union")
}
func (r UnionRepresentation_Stringprefix) GetDelim() string {
return r.delim
}
func (r UnionRepresentation_Stringprefix) GetDiscriminant(t Type) string {
for d, t2 := range r.table {
if t2 == t.Name() {
return d
}
}
panic("that type isn't a member of this union")
}
// GetMember returns type info for the member matching the kind argument,
// or may return nil if that kind is not mapped to a member of this union.
func (r UnionRepresentation_Kinded) GetMember(k datamodel.Kind) TypeName {
return r.table[k]
}
// Fields returns a slice of descriptions of the object's fields.
func (t TypeStruct) Fields() []StructField {
return t.fields
}
// Field looks up a StructField by name, or returns nil if no such field.
func (t TypeStruct) Field(name string) *StructField {
if v, ok := t.fieldsMap[name]; ok {
return &v
}
return nil
}
// Parent returns the type information that this field describes a part of.
//
// While in many cases, you may know the parent already from context,
// there may still be situations where want to pass around a field and
// not need to continue passing down the parent type with it; this method
// helps your code be less redundant in such a situation.
// (You'll find this useful for looking up any rename directives, for example,
// when holding onto a field, since that requires looking up information from
// the representation strategy, which is a property of the type as a whole.)
func (f StructField) Parent() *TypeStruct { return f.parent }
// Name returns the string name of this field. The name is the string that
// will be used as a map key if the structure this field is a member of is
// serialized as a map representation.
func (f StructField) Name() string { return f.name }
// Type returns the Type of this field's value. Note the field may
// also be unset if it is either Optional or Nullable.
func (f StructField) Type() Type { return f.parent.universe.namedTypes[f.typ] }
// IsOptional returns true if the field is allowed to be absent from the object.
// If IsOptional is false, the field may be absent from the serial representation
// of the object entirely.
//
// Note being optional is different than saying the value is permitted to be null!
// A field may be both nullable and optional simultaneously, or either, or neither.
func (f StructField) IsOptional() bool { return f.optional }
// IsNullable returns true if the field value is allowed to be null.
//
// If is Nullable is false, note that it's still possible that the field value
// will be absent if the field is Optional! Being nullable is unrelated to
// whether the field's presence is optional as a whole.
//
// Note that a field may be both nullable and optional simultaneously,
// or either, or neither.
func (f StructField) IsNullable() bool { return f.nullable }
// IsMaybe returns true if the field value is allowed to be either null or absent.
//
// This is a simple "or" of the two properties,
// but this method is a shorthand that turns out useful often.
func (f StructField) IsMaybe() bool { return f.nullable || f.optional }
func (t TypeStruct) RepresentationStrategy() StructRepresentation {
return t.representation
}
func (r StructRepresentation_Map) GetFieldKey(field StructField) string {
if n, ok := r.renames[field.name]; ok {
return n
}
return field.name
}
func (r StructRepresentation_Map) FieldHasRename(field StructField) bool {
_, ok := r.renames[field.name]
return ok
}
// FieldImplicit returns the 'implicit' value for a field, or nil, if there isn't one.
//
// Because this returns the golang ImplicitValue type, which is an interface,
// golang type switching is needed to distinguish what it holds.
// (In other words, be warned that this function is not very friendly to use from templating engines.)
func (r StructRepresentation_Map) FieldImplicit(field StructField) ImplicitValue {
if r.implicits == nil {
return nil
}
return r.implicits[field.name]
}
func (r StructRepresentation_Stringjoin) GetDelim() string {
return r.sep
}
// Members returns a slice the strings which are valid inhabitants of this enum.
func (t TypeEnum) Members() []string {
return t.members
}
func (t TypeEnum) RepresentationStrategy() EnumRepresentation {
return t.representation
}
// Links can keep a referenced type, which is a hint only about the data on the
// other side of the link, no something that can be explicitly validated without
// loading the link
// HasReferencedType returns true if the link has a hint about the type it references
// false if it's generic
func (t TypeLink) HasReferencedType() bool {
return t.hasReferencedType
}
// ReferencedType returns the type hint for the node on the other side of the link
func (t TypeLink) ReferencedType() Type {
return t.universe.namedTypes[t.referencedType]
}

View File

@@ -0,0 +1,85 @@
package schema
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// schema.TypedNode is a superset of the datamodel.Node interface, and has additional behaviors.
//
// A schema.TypedNode can be inspected for its schema.Type and schema.TypeKind,
// which conveys much more and richer information than the Data Model layer
// datamodel.Kind.
//
// There are many different implementations of schema.TypedNode.
// One implementation can wrap any other existing datamodel.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 datamodel.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 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 schema.TypedNode only and not their representations;
// continuing the example, the .Representation().LookupByString() method on
// that same node for the same key as plain `.LookupByString()` will still
// 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 datamodel.Kind it acts as is determined by the TypeKind.
// (Note that the representation strategy of the type does *not* affect
// the Kind of schema.TypedNode -- rather, the representation strategy
// affects the `.Representation().Kind()`.)
//
// For example: if the `.Type().TypeKind()` of this node is "struct",
// it will act like Kind() == "map"
// (even if Type().(Struct).ReprStrategy() is "tuple").
datamodel.Node
// Type returns a reference to the reified schema.Type value.
Type() Type
// Representation returns a datamodel.Node which sees the data in this node
// in its representation form.
//
// For example: if the `.Type().TypeKind()` of this node is "struct",
// `.Representation().TypeKind()` may vary based on its representation strategy:
// if the representation strategy is "map", then it will be Kind=="map";
// if the streatgy is "tuple", then it will be Kind=="list".
Representation() datamodel.Node
}
// schema.TypedLinkNode is a superset of the schema.TypedNode interface, and has one additional behavior.
//
// A schema.TypedLinkNode 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
// into a node representation and validated against the schema as quickly as possible
//
// So, for example, if you wanted to support loading the other side of a link
// with a code-gen'd node builder while utilizing the automatic loading facilities
// of the traversal package, you could write a LinkNodeBuilderChooser as follows:
//
// func LinkNodeBuilderChooser(lnk datamodel.Link, lnkCtx linking.LinkContext) datamodel.NodePrototype {
// if tlnkNd, ok := lnkCtx.LinkNode.(schema.TypedLinkNode); ok {
// return tlnkNd.LinkTargetNodePrototype()
// }
// return basicnode.Prototype.Any
// }
type TypedLinkNode interface {
LinkTargetNodePrototype() datamodel.NodePrototype
}
// TypedPrototype is a superset of the datamodel.Nodeprototype interface, and has
// additional behaviors, much like TypedNode for datamodel.Node.
type TypedPrototype interface {
datamodel.NodePrototype
// Type returns a reference to the reified schema.Type value.
Type() Type
// Representation returns a datamodel.NodePrototype for the representation
// form of the prototype.
Representation() datamodel.NodePrototype
}

View File

@@ -0,0 +1,18 @@
package schema
type TypeSystem struct {
// namedTypes is the set of all named types in this universe.
// The map's key is the value's Name() property and must be unique.
//
// The IsAnonymous property is false for all values in this map that
// support the IsAnonymous property.
//
// Each Type in the universe may only refer to other types in their
// definition if those type are either A) in this namedTypes map,
// or B) are IsAnonymous==true.
namedTypes map[TypeName]Type
// names are the same set of names stored in namedTypes,
// but in insertion order.
names []TypeName
}

View File

@@ -0,0 +1,70 @@
package schema
/*
Okay, so. There are several fun considerations for a "validate" method.
---
There's two radically different approaches to "validate"/"reify":
- Option 1: Look at the schema.Type info and check if a data node seems
to match it -- recursing on the type info.
- Option 2: Use the schema.Type{}.RepresentationNodeBuilder() to feed data
into it -- recursing on what the nodebuilder already expresses.
(Option 2 also need to take a `memStorage ipld.NodeBuilder` param, btw,
for handling all the cases where we *aren't* doing codegen.)
Option 1 provides a little more opportunity for returning multiple errors.
Option 2 will generally have a hard time with that (nodebuilers are not
necessarily in a valid state after their first error encounter).
As a result of having these two options at all, we may indeed end up with
at least two very different functions -- despite seeming to do similar
things, their interior will radically diverge.
---
We may also need to consider distinct reification paths: we may want one
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
which has expectations about pointer equality consistency.)
---
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 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.
There's a couple of different ways to handle some of the behaviors around
unions, and some of them make the tradeoff described above, and I'm really
unsure if all the implications have been sussed out yet. We should defer
writing code that depends on this issue until gathering some more info.
---
One more note: about returning multiple errors from a Validate function:
there's an upper bound of the utility of the thing. Going farther than the
first parse error is nice, but it will still hit limits: for example,
upon encountering a union and failing to match it, we can't generally
produce further errors from anywhere deeper in the tree without them being
combinatorial "if previous juncture X was type Y, then..." nonsense.
(This applies to all recursive kinds to some degree, but it's especially
rough with unions. For most of the others, it's flatly a missing field,
or an excessive field, or a leaf error; with unions it can be hard to tell.)
---
And finally: both "Validate" and "Reify" methods might actually belong
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.
*/

View File

@@ -0,0 +1,125 @@
Storage Adapters
================
The go-ipld-prime storage APIs were introduced in the v0.14.x ranges of go-ipld-prime,
which happened in fall 2021.
There are many other pieces of code in the IPLD (and even more so, the IPFS) ecosystem
which predate this, and have interfaces that are very _similar_, but not quite exactly the same.
In order to keep using that code, we've built a series of adapters.
You can see these in packages beneath this one:
- `go-ipld-prime/storage/bsadapter` is an adapter to `github.com/ipfs/go-ipfs-blockstore`.
- `go-ipld-prime/storage/dsadapter` is an adapter to `github.com/ipfs/go-datastore`.
- `go-ipld-prime/storage/bsrvadapter` is an adapter to `github.com/ipfs/go-blockservice`.
Note that there are also other packages which implement the go-ipld-prime storage APIs,
but are not considered "adapters" -- these just implement the storage APIs directly:
- `go-ipld-prime/storage/memstore` is a simple in-memory storage system.
- `go-ipld-prime/storage/fsstore` is a simple filesystem-backed storage system
(comparable to, and compatible with [flatfs](https://pkg.go.dev/github.com/ipfs/go-ds-flatfs),
if you're familiar with that -- but higher efficiency).
Finally, note that there are some shared benchmarks across all this:
- check out `go-ipld-prime/storage/benchmarks`!
Why structured like this?
-------------------------
### Why is there adapter code at all?
The `go-ipld-prime/storage` interfaces are a newer generation.
A new generation of APIs was desirable because it unifies the old APIs,
and also because we were able to improves and update several things in the process.
(You can see some of the list of improvements in https://github.com/ipld/go-ipld-prime/pull/265,
where these APIs were first introduced.)
The new generation of APIs avoids several types present in the old APIs which forced otherwise-avoidable allocations.
(See notes later in this document about "which adapter should I use" for more on that.)
Finally, the new generation of APIs is carefully designed to support minimal implementations,
by carefully avoiding use of non-standard-library types in key API definitions,
and by keeping most advanced features behind a standardized convention of feature detection.
Because the newer generation of APIs are not exactly the same as the multiple older APIs we're unifying and updating,
some amount of adapter code is necessary.
(Fortunately, it's not much! But it's not "none", either.)
### Why have this code in a shared place?
The glue code to connect `go-datastore` and the other older APIs
to the new `go-ipld-prime/storage` APIs is fairly minimal...
but there's also no reason for anyone to write it twice,
so we want to put it somewhere easy to share.
### Why do the adapters have their own go modules?
A separate module is used because it's important that go-ipld-prime can be used
without forming a dependency on `go-datastore` (or the other relevant modules, per adapter).
We want this so that there's a reasonable deprecation pathway -- it must be
possible to write new code that doesn't take on transitive dependencies to old code.
(As a bonus, looking at the module dependency graphs makes an interestingly
clear statement about why minimal APIs that don't force transitive dependencies are a good idea!)
### Why is this code all together in this repo?
We put these separate modules in the same git repo as `go-ipld-prime`... because we can.
Technically, neither the storage adapter modules nor the `go-ipld-prime` module depend on each other --
they just have interfaces that are aligned with each other -- so it's very easy to
hold them as separate go modules in the same repo, even though that can otherwise sometimes be tricky.
You may want to make a point of pulling updated versions of the storage adapters that you use
when pulling updates to go-ipld-prime, though.
### Could we put these adapters upstream into the other relevant repos?
Certainly!
We started with them here because it seemed developmentally lower-friction.
That may change; these APIs could move.
This code is just interface satisfaction, so even having multiple copies of it is utterly harmless.
Which of `dsadapter` vs `bsadapter` vs `bsrvadapter` should I use?
------------------------------------------------------------------
None of them, ideally.
A direct implementation of the storage APIs will almost certainly be able to perform better than any of these adapters.
(Check out the `fsstore` package, for example.)
Failing that: use the adapter matching whatever you've got on hand in your code.
There is no correct choice.
`dsadapter` suffers avoidable excessive allocs in processing its key type,
due to choices in the interior of `github.com/ipfs/go-datastore`.
It is also unable to support streaming operation, should you desire it.
`bsadapter` and `bsrvadapter` both also suffer overhead due to their key type,
because they require a transformation back from the plain binary strings used in the storage API to the concrete go-cid type,
which spends some avoidable CPU time (and also, at present, causes avoidable allocs because of some interesting absenses in `go-cid`).
Additionally, they suffer avoidable allocs because they wrap the raw binary data in a "block" type,
which is an interface, and thus heap-escapes; and we need none of that in the storage APIs, and just return the raw data.
They are also unable to support streaming operation, should you desire it.
It's best to choose the shortest path and use the adapter to whatever layer you need to get to --
for example, if you really want to use a `go-datastore` implementation,
*don't* use `bsadapter` and have it wrap a `go-blockstore` that wraps a `go-datastore` if you can help it:
instead, use `dsadapter` and wrap the `go-datastore` without any extra layers of indirection.
You should prefer this because most of the notes above about avoidable allocs are true when
the legacy interfaces are communicating with each other, as well...
so the less you use the internal layering of the legacy interfaces, the better off you'll be.
Using a direct implementation of the storage APIs will suffer none of these overheads,
and so will always be your best bet if possible.
If you have to use one of these adapters, hopefully the performance overheads fall within an acceptable margin.
If not: we'll be overjoyed to accept help porting things.

173
vendor/github.com/ipld/go-ipld-prime/storage/api.go generated vendored Normal file
View File

@@ -0,0 +1,173 @@
package storage
import (
"context"
"io"
)
// --- basics --->
// Storage is one of the base interfaces in the storage APIs.
// This type is rarely seen by itself alone (and never useful to implement alone),
// but is included in both ReadableStorage and WritableStorage.
// Because it's included in both the of the other two useful base interfaces,
// you can define functions that work on either one of them
// by using this type to describe your function's parameters.
//
// Library functions that work with storage systems should take either
// ReadableStorage, or WritableStorage, or Storage, as a parameter,
// depending on whether the function deals with the reading of data,
// or the writing of data, or may be found on either, respectively.
//
// An implementation of Storage may also support many other methods.
// At the very least, it should also support one of either ReadableStorage or WritableStorage.
// It may support even more interfaces beyond that for additional feature detection.
// See the package-wide docs for more discussion of this design.
//
// The Storage interface does not include much of use in itself alone,
// because ReadableStorage and WritableStorage are meant to be the most used types in declarations.
// However, it does include the Has function, because that function is reasonable to require ubiquitously from all implementations,
// and it serves as a reasonable marker to make sure the Storage interface is not trivially satisfied.
type Storage interface {
Has(ctx context.Context, key string) (bool, error)
}
// ReadableStorage is one of the base interfaces in the storage APIs;
// a storage system should implement at minimum either this, or WritableStorage,
// depending on whether it supports reading or writing.
// (One type may also implement both.)
//
// ReadableStorage implementations must at minimum provide
// a way to ask the store whether it contains a key,
// and a way to ask it to return the value.
//
// Library functions that work with storage systems should take either
// ReadableStorage, or WritableStorage, or Storage, as a parameter,
// depending on whether the function deals with the reading of data,
// or the writing of data, or may be found on either, respectively.
//
// An implementation of ReadableStorage may also support many other methods --
// for example, it may additionally match StreamingReadableStorage, or yet more interfaces.
// Usually, you should not need to check for this yourself; instead,
// you should use the storage package's functions to ask for the desired mode of interaction.
// Those functions will will accept any ReadableStorage as an argument,
// detect the additional interfaces automatically and use them if present,
// or, fall back to synthesizing equivalent behaviors from the basics.
// See the package-wide docs for more discussion of this design.
type ReadableStorage interface {
Storage
Get(ctx context.Context, key string) ([]byte, error)
}
// WritableStorage is one of the base interfaces in the storage APIs;
// a storage system should implement at minimum either this, or ReadableStorage,
// depending on whether it supports reading or writing.
// (One type may also implement both.)
//
// WritableStorage implementations must at minimum provide
// a way to ask the store whether it contains a key,
// and a way to put a value into storage indexed by some key.
//
// Library functions that work with storage systems should take either
// ReadableStorage, or WritableStorage, or Storage, as a parameter,
// depending on whether the function deals with the reading of data,
// or the writing of data, or may be found on either, respectively.
//
// An implementation of WritableStorage may also support many other methods --
// for example, it may additionally match StreamingWritableStorage, or yet more interfaces.
// Usually, you should not need to check for this yourself; instead,
// you should use the storage package's functions to ask for the desired mode of interaction.
// Those functions will will accept any WritableStorage as an argument,
// detect the additional interfaces automatically and use them if present,
// or, fall back to synthesizing equivalent behaviors from the basics.
// See the package-wide docs for more discussion of this design.
type WritableStorage interface {
Storage
Put(ctx context.Context, key string, content []byte) error
}
// --- streaming --->
type StreamingReadableStorage interface {
GetStream(ctx context.Context, key string) (io.ReadCloser, error)
}
// StreamingWritableStorage is a feature-detection interface that advertises support for streaming writes.
// It is normal for APIs to use WritableStorage in their exported API surface,
// and then internally check if that value implements StreamingWritableStorage if they wish to use streaming operations.
//
// Streaming writes can be preferable to the all-in-one style of writing of WritableStorage.Put,
// because with streaming writes, the high water mark for memory usage can be kept lower.
// On the other hand, streaming writes can incur slightly higher allocation counts,
// which may cause some performance overhead when handling many small writes in sequence.
//
// The PutStream function returns three parameters: an io.Writer (as you'd expect), another function, and an error.
// The function returned is called a "WriteCommitter".
// The final error value is as usual: it will contain an error value if the write could not be begun.
// ("WriteCommitter" will be refered to as such throughout the docs, but we don't give it a named type --
// unfortunately, this is important, because we don't want to force implementers of storage systems to import this package just for a type name.)
//
// The WriteCommitter function should be called when you're done writing,
// at which time you give it the key you want to commit the data as.
// It will close and flush any streams, and commit the data to its final location under this key.
// (If the io.Writer is also an io.WriteCloser, it is not necessary to call Close on it,
// because using the WriteCommiter will do this for you.)
//
// Because these storage APIs are meant to work well for content-addressed systems,
// the key argument is not provided at the start of the write -- it's provided at the end.
// (This gives the opportunity to be computing a hash of the contents as they're written to the stream.)
//
// As a special case, giving a key of the zero string to the WriteCommiter will
// instead close and remove any temp files, and store nothing.
// An error may still be returned from the WriteCommitter if there is an error cleaning up
// any temporary storage buffers that were created.
//
// Continuing to write to the io.Writer after calling the WriteCommitter function will result in errors.
// Calling the WriteCommitter function more than once will result in errors.
type StreamingWritableStorage interface {
PutStream(ctx context.Context) (io.Writer, func(key string) error, error)
}
// --- other specializations --->
// VectorWritableStorage is an API for writing several slices of bytes at once into storage.
// It's meant a feature-detection interface; not all storage implementations need to provide this feature.
// This kind of API can be useful for maximizing performance in scenarios where
// data is already loaded completely into memory, but scattered across several non-contiguous regions.
type VectorWritableStorage interface {
PutVec(ctx context.Context, key string, blobVec [][]byte) error
}
// PeekableStorage is a feature-detection interface which a storage implementation can use to advertise
// the ability to look at a piece of data, and return it in shared memory.
// The PeekableStorage.Peek method is essentially the same as ReadableStorage.Get --
// but by contrast, ReadableStorage is expected to return a safe copy.
// PeekableStorage can be used when the caller knows they will not mutate the returned slice.
//
// An io.Closer is returned along with the byte slice.
// The Close method on the Closer must be called when the caller is done with the byte slice;
// otherwise, memory leaks may result.
// (Implementers of this interface may be expecting to reuse the byte slice after Close is called.)
//
// Note that Peek does not imply that the caller can use the byte slice freely;
// doing so may result in storage corruption or other undefined behavior.
type PeekableStorage interface {
Peek(ctx context.Context, key string) ([]byte, io.Closer, error)
}
// the following are all hypothetical additional future interfaces (in varying degress of speculativeness):
// FUTURE: an EnumerableStorage API, that lets you list all keys present?
// FUTURE: a cleanup API (for getting rid of tmp files that might've been left behind on rough shutdown)?
// FUTURE: a sync-forcing API?
// FUTURE: a delete API? sure. (just document carefully what its consistency model is -- i.e. basically none.)
// (hunch: if you do want some sort of consistency model -- consider offering a whole family of methods that have some sort of generation or sequencing number on them.)
// FUTURE: a force-overwrite API? (not useful for a content-address system. but maybe a gesture towards wider reusability is acceptable to have on offer.)
// FUTURE: a size estimation API? (unclear if we need to standardize this, but we could. an offer, anyway.)
// FUTURE: a GC API? (dubious -- doing it well probably crosses logical domains, and should not be tied down here.)

75
vendor/github.com/ipld/go-ipld-prime/storage/doc.go generated vendored Normal file
View File

@@ -0,0 +1,75 @@
// The storage package contains interfaces for storage systems, and functions for using them.
//
// These are very low-level storage primitives.
// The interfaces here deal only with raw keys and raw binary blob values.
//
// In IPLD, you can often avoid dealing with storage directly yourself,
// and instead use linking.LinkSystem to handle serialization, hashing, and storage all at once.
// (You'll hand some values that match interfaces from this package to LinkSystem when configuring it.)
// It's probably best to work at that level and above as much as possible.
// If you do need to interact with storage more directly, the read on.
//
// The most basic APIs are ReadableStorage and WritableStorage.
// When writing code that works with storage systems, these two interfaces should be seen in almost all situations:
// user code is recommended to think in terms of these types;
// functions provided by this package will accept parameters of these types and work on them;
// implementations are expected to provide these types first;
// and any new library code is recommended to keep with the theme: use these interfaces preferentially.
//
// Users should decide which actions they want to take using a storage system,
// find the appropriate function in this package (n.b., package function -- not a method on an interface!
// You will likely find one of each, with the same name: pick the package function!),
// and use that function, providing it the storage system (e.g. either ReadableStorage, WritableStorage, or sometimes just Storage)
// as a parameter.
// That function will then use feature-detection (checking for matches to the other,
// more advanced and more specific interfaces in this package) and choose the best way
// to satisfy the request; or, if it can't feature-detect any relevant features,
// the function will fall back to synthesizing the requested behavior out of the most basic API.
// Using the package functions, and letting them do the feature detection for you,
// should provide the most consistent user experience and minimize the amount of work you need to do.
// (Bonus: It also gives us a convenient place to smooth out any future library migrations for you!)
//
// If writing new APIs that are meant to work reusably for any storage implementation:
// APIs should usually be designed around accepting ReadableStorage or WritableStorage as parameters
// (depending on which direction of data flow the API is regarding).
// and use the other interfaces (e.g. StreamingReadableStorage) thereafter internally for feature detection.
// For APIs which may sometimes be found relating to either a read or a write direction of data flow,
// the Storage interface may be used in order to define a function that should accept either ReadableStorage or WritableStorage.
// In other words: when writing reusable APIs, one should follow the same pattern as this package's own functions do.
//
// Similarly, implementers of storage systems should always implement either ReadableStorage or WritableStorage first.
// Only after satisfying one of those should the implementation then move on to further supporting
// additional interfaces in this package (all of which are meant to support feature-detection).
// Beyond one of the basic two, all the other interfaces are optional:
// you can implement them if you want to advertise additional features,
// or advertise fastpaths that your storage system supports;
// but you don't have implement any of those additional interfaces if you don't want to,
// or if your implementation can't offer useful fastpaths for them.
//
// Storage systems as described by this package are allowed to make some interesting trades.
// Generally, write operations are allowed to be first-write-wins.
// Furthermore, there is no requirement that the system return an error if a subsequent write to the same key has different content.
// These rules are reasonable for a content-addressed storage system, and allow great optimizations to be made.
//
// Note that all of the interfaces in this package only use types that are present in the golang standard library.
// This is intentional, and was done very carefully.
// If implementing a storage system, you should find it possible to do so *without* importing this package.
// Because only standard library types are present in the interface contracts,
// it's possible to implement types that align with the interfaces without refering to them.
//
// Note that where keys are discussed in this package, they use the golang string type --
// however, they may be binary. (The golang string type allows arbitrary bytes in general,
// and here, we both use that, and explicitly disavow the usual "norm" that the string type implies UTF-8.
// This is roughly the same as the practical truth that appears when using e.g. os.OpenFile and other similar functions.)
// If you are creating a storage implementation where the underlying medium does not support arbitrary binary keys,
// then it is strongly recommend that your storage implementation should support being configured with
// an "escaping function", which should typically simply be of the form `func(string) string`.
// Additional, your storage implementation's documentation should also clearly describe its internal limitations,
// so that users have enough information to write an escaping function which
// maps their domain into the domain your storage implementation can handle.
package storage
// also note:
// LinkContext stays *out* of this package. It's a chooser-related thing.
// LinkSystem can think about it (and your callbacks over there can think about it), and that's the end of its road.
// (Future: probably LinkSystem should have SetStorage and SetupStorageChooser methods for helping you set things up -- where the former doesn't discuss LinkContext at all.)

122
vendor/github.com/ipld/go-ipld-prime/storage/funcs.go generated vendored Normal file
View File

@@ -0,0 +1,122 @@
package storage
import (
"bytes"
"context"
"fmt"
"io"
)
/*
This file contains equivalents of every method that can be feature-detected on a storage system.
You can always call these functions, and give them the most basic storage interface,
and they'll attempt to feature-detect their way to the best possible implementation of the behavior,
or they'll fall back to synthesizing the same behavior from more basic interfaces.
Long story short: you can always use these functions as an end user, and get the behavior you want --
regardless of how much explicit support the storage implementation has for the exact behavior you requested.
*/
func Has(ctx context.Context, store Storage, key string) (bool, error) {
// Okay, not much going on here -- this function is only here for consistency of style.
return store.Has(ctx, key)
}
func Get(ctx context.Context, store ReadableStorage, key string) ([]byte, error) {
// Okay, not much going on here -- this function is only here for consistency of style.
return store.Get(ctx, key)
}
func Put(ctx context.Context, store WritableStorage, key string, content []byte) error {
// Okay, not much going on here -- this function is only here for consistency of style.
return store.Put(ctx, key, content)
}
// GetStream returns a streaming reader.
// This function will feature-detect the StreamingReadableStorage interface, and use that if possible;
// otherwise it will fall back to using basic ReadableStorage methods transparently
// (at the cost of loading all the data into memory at once and up front).
func GetStream(ctx context.Context, store ReadableStorage, key string) (io.ReadCloser, error) {
// Prefer the feature itself, first.
if streamable, ok := store.(StreamingReadableStorage); ok {
return streamable.GetStream(ctx, key)
}
// Fallback to basic.
blob, err := store.Get(ctx, key)
return noopCloser{bytes.NewReader(blob)}, err
}
// PutStream returns an io.Writer and a WriteCommitter callback.
// (See the docs on StreamingWritableStorage.PutStream for details on what that means.)
// This function will feature-detect the StreamingWritableStorage interface, and use that if possible;
// otherwise it will fall back to using basic WritableStorage methods transparently
// (at the cost of needing to buffer all of the content in memory while the write is in progress).
func PutStream(ctx context.Context, store WritableStorage) (io.Writer, func(key string) error, error) {
// Prefer the feature itself, first.
if streamable, ok := store.(StreamingWritableStorage); ok {
return streamable.PutStream(ctx)
}
// Fallback to basic.
var buf bytes.Buffer
var written bool
return &buf, func(key string) error {
if written {
return fmt.Errorf("WriteCommitter already used")
}
written = true
return store.Put(ctx, key, buf.Bytes())
}, nil
}
// PutVec is an API for writing several slices of bytes at once into storage.
// This kind of API can be useful for maximizing performance in scenarios where
// data is already loaded completely into memory, but scattered across several non-contiguous regions.
// This function will feature-detect the VectorWritableStorage interface, and use that if possible;
// otherwise it will fall back to using StreamingWritableStorage,
// or failing that, fall further back to basic WritableStorage methods, transparently.
func PutVec(ctx context.Context, store WritableStorage, key string, blobVec [][]byte) error {
// Prefer the feature itself, first.
if putvable, ok := store.(VectorWritableStorage); ok {
return putvable.PutVec(ctx, key, blobVec)
}
// Fallback to streaming mode.
// ... or, fallback to basic, and use emulated streaming. Still presumably preferable to doing a big giant memcopy.
// Conveniently, the PutStream function makes that transparent for our implementation, too.
wr, wrcommit, err := PutStream(ctx, store)
if err != nil {
return err
}
for _, blob := range blobVec {
_, err := wr.Write(blob)
if err != nil {
return err
}
}
return wrcommit(key)
}
// Peek accessess the same data as Get, but indicates that the caller promises not to mutate the returned byte slice.
// (By contrast, Get is expected to return a safe copy.)
// This function will feature-detect the PeekableStorage interface, and use that if possible;
// otherwise it will fall back to using basic ReadableStorage methods transparently
// (meaning that a no-copy fastpath simply wasn't available).
//
// An io.Closer is returned along with the byte slice.
// The Close method on the Closer must be called when the caller is done with the byte slice;
// otherwise, memory leaks may result.
// (Implementers of this interface may be expecting to reuse the byte slice after Close is called.)
func Peek(ctx context.Context, store ReadableStorage, key string) ([]byte, io.Closer, error) {
// Prefer the feature itself, first.
if peekable, ok := store.(PeekableStorage); ok {
return peekable.Peek(ctx, key)
}
// Fallback to basic.
bs, err := store.Get(ctx, key)
return bs, noopCloser{nil}, err
}
type noopCloser struct {
io.Reader
}
func (noopCloser) Close() error { return nil }

3
vendor/github.com/ipld/go-ipld-prime/version.json generated vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"version": "v0.20.0"
}