Comment DSL
We have a comment DSL to help annotate the output code beyond what is possible just with CDDL.
@name
For example in an array-encoded group you can give explicit names just by the keys e.g.:
foo = [
bar: uint,
baz: text
]
but with map-encoded structs the keys are stored and for things like integer keys this isn't very helpful e.g.:
tx = {
? 0: [* input],
? 1: [* outputs],
}
we would end up with two fields: key_0
and key_1
. We can instead end up with fields named inputs
and outputs
by doing:
tx = {
? 0: [* input], ; @name inputs
? 1: [* outputs], ; @name outputs
}
Note: the parsing can be finicky. For struct fields you must put the comment AFTER the comma, and the comma must exist even for the last field in a struct.
It is also possible to use @name
with type choices:
foo = 0 ; @name mainnet
/ 1 ; @name testnet
and also for group choices:
script = [
; @name native
tag: 0, script: native_script //
; @name plutus_v1
tag: 1, script: plutus_v1_script //
; @name plutus_v2
tag: 2, script: plutus_v2_script
]
@newtype
With code like foo = uint
this creates an alias e.g. pub type Foo = u64;
in rust. When we use foo = uint ; @newtype
it instead creates a pub struct Foo(u64);
.
@newtype
can also optionally specify a getter function e.g. foo = uint ; @newtype custom_getter
will generate:
impl Foo {
pub fn custom_getter(&self) -> u64 {
self.0
}
}
@no_alias
foo = uint
bar = [
field: foo
]
This would normally result in:
pub type Foo = u64;
pub struct Bar {
field: Foo,
}
but if we use @no_alias
it skips generating an alias and uses it directly e.g.:
foo = uint ; @no_alias
bar = [
field: foo
]
to
pub struct Bar {
field: u64,
}
@used_as_key
foo = [
x: uint,
y: uint,
] ; @used_as_Key
cddl-codegen automatically derives Ord
/PartialOrd
or Hash
for any types used within as a key in another type.
Putting this comment on a type forces that type to derive those traits even if it weren't used in a key in the cddl spec.
This is useful for when you are writing utility code that would put them in a map and want the generated code to have it already,
which is particularly useful for re-generating as it lets your mod.rs
files remain untouched.
@custom_json
foo = uint ; @newtype @custom_json
Avoids generating and/or deriving json-related traits under the assumption that the user will supply their own implementation to be used in the generated library.
@custom_serialize / @custom_deserialize
custom_bytes = bytes ; @custom_serialize custom_serialize_bytes @custom_deserialize custom_deserialize_bytes
struct_with_custom_serialization = [
custom_bytes,
field: bytes, ; @custom_serialize custom_serialize_bytes @custom_deserialize custom_deserialize_bytes
overridden: custom_bytes, ; @custom_serialize write_hex_string @custom_deserialize read_hex_string
tagged1: #6.9(custom_bytes),
tagged2: #6.9(uint), ; @custom_serialize write_tagged_uint_str @custom_deserialize read_tagged_uint_str
]
This allows the overriding of serialization and/or deserialization for when a specific format must be maintained. This works even with primitives where CDDL_CODEGEN_EXTERN_TYPE would require making a wrapper type to use.
The string after @custom_serialize
/@custom_deserialize
will be directly called as a function in place of regular serialization/deserialization code. As such it must either be specified using fully qualified paths e.g. @custom_serialize crate::utils::custom_serialize_function
, or post-generation it will need to be imported into the serialization code by hand e.g. adding import crate::utils::custom_serialize_function;
.
With --preserve-encodings=true
the encoding variables must be passed in in the order they are used in cddl-codegen with regular serialization. They are passed in as Option<cbor_event::Sz>
for integers/tags, LenEncoding
for lengths and StringEncoding
for text/bytes. These are the same types as are stored in the *Encoding
structs generated. The same must be returned for deserialization. When there are no encoding variables the deserialized value should be directly returned, and if not a tuple with the value and its encoding variables should be returned.
There are two ways to use this comment DSL:
- Type level: e.g.
custom_bytes
. This will replace the (de)serialization everywhere you use this type. - Field level: e.g.
struct_with_custom_serialization.field
. This will entirely replace the (de)serialization logic for the entire field, including other encoding operations like tags,.cbor
, etc.
Example function signatures for --preserve-encodings=false
for custom_serialize_bytes
/ custom_deserialize_bytes
above:
pub fn custom_serialize_bytes<'se, W: std::io::Write>(
serializer: &'se mut cbor_event::se::Serializer<W>,
bytes: &[u8],
) -> cbor_event::Result<&'se mut cbor_event::se::Serializer<W>>
pub fn custom_deserialize_bytes<R: std::io::BufRead + std::io::Seek>(
raw: &mut cbor_event::de::Deserializer<R>,
) -> Result<Vec<u8>, DeserializeError>
Example function signatures for --preserve-encodings=true
for write_tagged_uint_str
/ read_tagged_uint_str
above:
pub fn write_tagged_uint_str<'se, W: std::io::Write>(
serializer: &'se mut cbor_event::se::Serializer<W>,
uint: &u64,
tag_encoding: Option<cbor_event::Sz>,
text_encoding: Option<cbor_event::Sz>,
) -> cbor_event::Result<&'se mut cbor_event::se::Serializer<W>>
pub fn read_tagged_uint_str<R: std::io::BufRead + std::io::Seek>(
raw: &mut cbor_event::de::Deserializer<R>,
) -> Result<(u64, Option<cbor_event::Sz>, Option<cbor_event::Sz>), DeserializeError>
Note that as this is at the field-level it must handle the tag as well as the uint
.
For more examples see tests/custom_serialization
(used in the core
and core_no_wasm
tests) and tests/custom_serialization_preserve
(used in the preserve-encodings
test).
@doc
This can be placed at field-level, struct-level or variant-level to specify a comment to be placed as a rust doc-comment.
docs = [
foo: text, ; @doc this is a field-level comment
bar: uint, ; @doc bar is a u64
] ; @doc struct documentation here
docs_groupchoice = [
; @name first @doc comment-about-first
0, uint //
; @doc comments about second @name second
text
] ; @doc type-level comment
Will generate:
/// struct documentation here
#[derive(Clone, Debug)]
pub struct Docs {
/// this is a field-level comment
pub foo: String,
/// bar is a u64
pub bar: u64,
}
impl Docs {
/// * `foo` - this is a field-level comment
/// * `bar` - bar is a u64
pub fn new(foo: String, bar: u64) -> Self {
Self { foo, bar }
}
}
/// type-level comment
#[derive(Clone, Debug)]
pub enum DocsGroupchoice {
/// comment-about-first
First(u64),
/// comments about second
Second(String),
}
Due to the comment dsl parsing this doc comment cannot contain the character @
.
CDDL_CODEGEN_EXTERN_TYPE
While not as a comment, this allows you to compose in hand-written structs into a cddl spec.
foo = _CDDL_CODEGEN_EXTERN_TYPE_
bar = [
x: uint,
y: foo,
]
This will treat Foo
as a type that will exist and that has implemented the Serialize
and Deserialize
traits, so the (de)serialization logic in Bar
here will call Foo::serialize()
and Foo::deserialize()
.
This can also be useful when you have a spec that is either very awkward to use (so you hand-write or hand-modify after generation) in some type so you don't generate those types and instead manually merge those hand-written/hand-modified structs back in to the code afterwards. This saves you from having to manually remove all code that is generated regarding Foo
first before merging in your own.
This can also be useful when you have a spec that is either very awkward to use (so you hand-write or hand-modify after generation) in some type so you don't generate those types and instead manually merge those hand-written/hand-modified structs back in to the code afterwards. This saves you from having to manually remove all code that is generated regarding Foo
first before merging in your own.
This also works with generics e.g. you can refer to foo<T>
. As with other generics this will create a pub type FooT = Foo<T>;
definition in rust to work with wasm-bindgen's restrictions (no generics) as on the wasm side there will be references to a FooT
in wasm. The wasm type definition is not emitted as that will be implementation-dependent. For an example see extern_generic
in the core
unit test.
CDDL_CODEGEN_RAW_BYTES_TYPE
Allows encoding as bytes
but imposing hand-written constraints defined elsewhere.
foo = _CDDL_CODEGEN_RAW_BYTES_TYPE_
bar = [
foo,
]
This will treat foo
as some external type called Foo
. This type must implement the exported (in serialization.rs
) trait RawBytesEncoding
.
This can be useful for example when working with cryptographic primitives e.g. a hash or pubkey, as it allows users to have those crypto structs be from a crypto library then they only need to implement the trait for them and they will be able to be directly used without needing any useless generated wrapper struct for the in between.