Derive macro for From<T> for Ipld
and TryFrom<Ipld> for T
#508
Labels
enhancement
New feature or request
From<T> for Ipld
and TryFrom<Ipld> for T
#508
To support encoding and decoding our types using
dag-json
anddag-cbor
, as well as for internal, generalIpld
use, we manually implement conversions for our struct types to/fromIpld
. This is something we should be able to clean up considerably with some derive macros.The current approach
Right now, wiring up a type to be encoded to and from
dag-json
anddag-cbor
involves three steps:From<T> for Ipld
TryFrom<Ipld> for T
DagJson for T
DagCbor for T
For example, here's what those first two implementations look like for the
Receipt<Ipld>
type:These implementations tend to be fairly rote and repetitive and primarily involve either constructing an
Ipld::Map
of the fields to be encoded, or it involves checking anIpld::Map
for all of the required keys, and then constructing the corresponding struct or enum.Given these two definitions, we're able to implement our
DagJson
trait for a type, which will provide default implementations of methods for encoding the type todag-json
, or for decodingdag-json
into our type:This works similarly for
DagCbor
as well.Proposed Approach
We think there's a relatively straightforward approach to deriving most of this code using a few macros, and a slight shift in how we handle encoding/decoding. Roughly:
Ipld
, for deriving the requiredFrom
andTryFrom
implementations.DagJson
trait, and instead expose generic functions over any type that implementslibipld-core::codec::Encode<DagJsonCodec>
orlibipldcore::codec::Decode<DagJsonCodec>
.DagCbor
derive and expose a genericto_cid
function over any type that implementslibipld-core::codec::Encode<DagCborCodec>
orlibipldcore::codec::Decode<DagCborCodec>
, replacing our internal type. This should be possible (e.g. https://github.com/Actyx/banyan/blob/master/banyan/src/index.rs#L477).DagJson
, for deriving the aboveEncode<DagJsonCodec>
andDecode<DagJsonCodec>
implementations.Ipld Derive Macro
This will be a fairly involved macro, but there is some prior art that we can crib a ton of code from.
In particular,
libipld
already provides a derive macro, DagCbor, for generating encode and decode implementations for a type under thedag-cbor
encoding. It takes a similar approach to what we'll need to do, however, you'll note that it's directly handling the translation to and fromdag-cbor
, rather than going throughIpld
first. This is unfortunately not something that we'll be able to easily do, asDagJsonCodec
is implemented using Serde, for theIpld
type specifically. Due to this, I think we'll just want to walk through the enum variants or struct fields in much the same way, and instead derivingTryFrom<Ipld>
andFrom<T> for Ipld
, rather than targetingDagJson
directly. These implementations can look essentially the same as what we've been writing manually.The other main complication here is that we'll want to support honoring the common
serde
attributes, likeserde(skip)
orserde(rename = "fooBar")
, during this translation, so that we're able to easily support common transformations, like encoding fields as camelCase.Existing DagJson Trait
We're overconstraining implementers of our existing
DagJson
type, by requiringT: TryFrom<Ipld>
andIpld: From<T>
on our encode + decode methods.Instead, we can delete this trait entirely, and expose helpers that directly invoke
Encode<DagJsonCodec>::encode
andDecode<DagJsonCodec>::decode
. Since these traits are already implemented forIpld
, we'll later be able to leverage this fact to support encoding and decoding types that can be converted toIpld
.This keeps things a little more ergonomic by directly tapping into the existing
libipld
traits, and thus automatically supporting anything that implements them, rather than needing to manually enumerate the encodable and decodable types in our codebase.DagJson Derive Macro
This would be a macro that generates
Encode<DagJsonCodec>
andDecode<DagJsonCodec>
implementations for a type, such thatT: TryFrom<Ipld>
andIpld: From<T>
.In the
Encode
case, it converts the value fromT
toIpld
, and then dispatches to theEncode
implementation forIpld
.In the
Decode
case, it decodes toIpld
, and then converts the resulting value to one of typeT
.This macro essentially fulfills the same role as the old
DagJson
trait, which primarily existed to mark types as being encodable or decodable, but while doing so in a way that offers better interop withlibipld
codebase.Testing Notes
Since the
Ipld
macro should generate nearly identical code to what we'd manually write today, all of our existing implementations should be lifted into conformance tests for the macro to provide a relatively risk-free migration path.The text was updated successfully, but these errors were encountered: