From 62ac3d6e0ae1e26a8be0520e6eb331e8b7b0e35e Mon Sep 17 00:00:00 2001 From: German Date: Tue, 25 Apr 2023 22:17:47 +0100 Subject: [PATCH] Schema generation (#178) --- .github/workflows/rust.yml | 1 + Cargo.toml | 10 ++++++++-- src/form.rs | 28 ++++++++++++++++++++++++++-- src/interner.rs | 18 +++++++++++++++++- src/lib.rs | 6 +++--- src/meta_type.rs | 12 ++++++++++++ src/portable.rs | 2 ++ src/ty/composite.rs | 1 + src/ty/fields.rs | 1 + src/ty/mod.rs | 9 +++++++++ src/ty/path.rs | 1 + src/ty/variant.rs | 2 ++ 12 files changed, 83 insertions(+), 8 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b875a59a..1cc93ad2 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -43,6 +43,7 @@ jobs: cargo check --no-default-features --features docs cargo check --no-default-features --features serde cargo check --no-default-features --features serde,decode + cargo check --no-default-features --features schema - name: build run: | diff --git a/Cargo.toml b/Cargo.toml index 1f9fbde1..6037f6b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ scale-info-derive = { version = "2.5.0", path = "derive", default-features = fal serde = { version = "1", default-features = false, optional = true, features = ["derive", "alloc"] } derive_more = { version = "0.99.1", default-features = false, features = ["from"] } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +schemars = { version = "0.8", optional = true } [features] default = ["std"] @@ -35,14 +36,19 @@ derive = [ docs = [ "scale-info-derive/docs" ] -# enables decoding and deserialization of portable scale-info type metadata +# Enables decoding and deserialization of portable scale-info type metadata. decode = [ "scale/full" ] -# enables type information for bitvec types, matching the name of the parity-scale-codec feature +# Enables type information for bitvec types, matching the name of the parity-scale-codec feature. bit-vec = [ "bitvec" ] +# Enables JSON Schema generation. +schema = [ + "std", + "schemars" +] [workspace] members = [ diff --git a/src/form.rs b/src/form.rs index 96ad1ff0..63c41ea7 100644 --- a/src/form.rs +++ b/src/form.rs @@ -39,9 +39,18 @@ use crate::{ meta_type::MetaType, }; +#[cfg(feature = "schema")] +use schemars::JsonSchema; #[cfg(feature = "serde")] use serde::Serialize; +/// Trait to support derivation of `JsonSchema` for schema generation. +#[cfg(feature = "schema")] +pub trait JsonSchemaMaybe: JsonSchema {} +/// Trait to support derivation of `JsonSchema` for schema generation. +#[cfg(not(feature = "schema"))] +pub trait JsonSchemaMaybe {} + /// Trait to control the internal structures of type definitions. /// /// This allows for type-level separation between free forms that can be @@ -49,15 +58,23 @@ use serde::Serialize; /// interning data structures. pub trait Form { /// The type representing the type. - type Type: PartialEq + Eq + PartialOrd + Ord + Clone + Debug; + type Type: PartialEq + Eq + PartialOrd + Ord + Clone + Debug + JsonSchemaMaybe; /// The string type. - type String: AsRef + PartialEq + Eq + PartialOrd + Ord + Clone + Debug; + type String: AsRef + + PartialEq + + Eq + + PartialOrd + + Ord + + Clone + + Debug + + JsonSchemaMaybe; } /// A meta meta-type. /// /// Allows to be converted into other forms such as portable form /// through the registry and `IntoPortable`. +#[cfg_attr(feature = "schema", derive(JsonSchema))] #[cfg_attr(feature = "serde", derive(Serialize))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)] pub enum MetaForm {} @@ -74,6 +91,7 @@ impl Form for MetaForm { /// This resolves some lifetime issues with self-referential structs (such as /// the registry itself) but can no longer be used to resolve to the original /// underlying data. +#[cfg_attr(feature = "schema", derive(JsonSchema))] #[cfg_attr(feature = "serde", derive(Serialize))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)] pub enum PortableForm {} @@ -92,3 +110,9 @@ cfg_if::cfg_if! { } } } + +// Blanket implementations +#[cfg(not(feature = "schema"))] +impl JsonSchemaMaybe for T {} +#[cfg(feature = "schema")] +impl JsonSchemaMaybe for T where T: JsonSchema {} diff --git a/src/interner.rs b/src/interner.rs index 1d7b3d1c..b8667b8a 100644 --- a/src/interner.rs +++ b/src/interner.rs @@ -37,6 +37,9 @@ use serde::{ Serialize, }; +#[cfg(feature = "schema")] +use schemars::JsonSchema; + /// A symbol that is not lifetime tracked. /// /// This can be used by self-referential types but @@ -74,13 +77,25 @@ impl From for UntrackedSymbol { } } +#[cfg(feature = "schema")] +impl JsonSchema for UntrackedSymbol { + fn schema_name() -> String { + String::from("UntrackedSymbol") + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + gen.subschema_for::() + } +} + /// A symbol from an interner. /// /// Can be used to resolve to the associated instance. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", serde(transparent))] -pub struct Symbol<'a, T> { +#[cfg_attr(feature = "schema", derive(JsonSchema))] +pub struct Symbol<'a, T: 'a> { id: u32, #[cfg_attr(feature = "serde", serde(skip))] marker: PhantomData &'a T>, @@ -122,6 +137,7 @@ impl Symbol<'_, T> { #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", serde(transparent))] +#[cfg_attr(feature = "schema", derive(JsonSchema))] pub struct Interner { /// A mapping from the interned elements to their respective space-efficient /// identifiers. diff --git a/src/lib.rs b/src/lib.rs index 4fa4dbae..90613705 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -340,9 +340,6 @@ mod registry; mod ty; mod utils; -#[cfg(test)] -mod tests; - #[doc(hidden)] pub use scale; @@ -395,3 +392,6 @@ where { MetaType::new::() } + +#[cfg(test)] +mod tests; diff --git a/src/meta_type.rs b/src/meta_type.rs index a1e92cd5..cf9fc270 100644 --- a/src/meta_type.rs +++ b/src/meta_type.rs @@ -111,3 +111,15 @@ impl MetaType { self == &MetaType::new::() } } + +#[cfg(feature = "schema")] +impl schemars::JsonSchema for MetaType { + fn schema_name() -> String { + "MetaType".into() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + // since MetaType does not really get serialized, we don't care about its actual schema + gen.subschema_for::() + } +} diff --git a/src/portable.rs b/src/portable.rs index ca0b51de..133f0594 100644 --- a/src/portable.rs +++ b/src/portable.rs @@ -41,6 +41,7 @@ use crate::{ use scale::Encode; /// A read-only registry containing types in their portable form for serialization. +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(all(feature = "serde", feature = "decode"), derive(serde::Deserialize))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] @@ -231,6 +232,7 @@ impl PortableRegistry { } /// Represent a type in it's portable form. +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(all(feature = "serde", feature = "decode"), derive(serde::Deserialize))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] diff --git a/src/ty/composite.rs b/src/ty/composite.rs index de18b503..8bd48b6e 100644 --- a/src/ty/composite.rs +++ b/src/ty/composite.rs @@ -69,6 +69,7 @@ use serde::{ )] #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, From, Encode)] pub struct TypeDefComposite { /// The fields of the composite type. diff --git a/src/ty/fields.rs b/src/ty/fields.rs index 9e944e79..f99faea6 100644 --- a/src/ty/fields.rs +++ b/src/ty/fields.rs @@ -71,6 +71,7 @@ use serde::{ )] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Encode)] pub struct Field { /// The name of the field. None for unnamed fields. diff --git a/src/ty/mod.rs b/src/ty/mod.rs index 08375ead..5843c372 100644 --- a/src/ty/mod.rs +++ b/src/ty/mod.rs @@ -61,6 +61,7 @@ pub use self::{ )] #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, From, Debug, Encode)] pub struct Type { /// The unique path to the type. Can be empty for built-in types @@ -207,6 +208,7 @@ where )) )] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, From, Debug, Encode)] pub struct TypeParameter { /// The name of the generic type parameter e.g. "T". @@ -295,6 +297,7 @@ where )] #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Encode)] pub enum TypeDef { /// A composite type (e.g. a struct or a tuple) @@ -369,6 +372,7 @@ impl IntoPortable for TypeDef { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Debug)] pub enum TypeDefPrimitive { /// `bool` type @@ -421,6 +425,7 @@ pub enum TypeDefPrimitive { /// An array type. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Debug)] pub struct TypeDefArray { /// The length of the array type. @@ -481,6 +486,7 @@ where )] #[cfg_attr(feature = "serde", serde(transparent))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Debug)] pub struct TypeDefTuple { /// The types of the tuple fields. @@ -546,6 +552,7 @@ where /// A type to refer to a sequence of elements of the same type. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Debug)] pub struct TypeDefSequence { /// The element type of the sequence type. @@ -600,6 +607,7 @@ where /// A type wrapped in [`Compact`]. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Debug)] pub struct TypeDefCompact { /// The type wrapped in [`Compact`], i.e. the `T` in `Compact`. @@ -644,6 +652,7 @@ where /// enabled, but can be decoded or deserialized into the `PortableForm` without this feature. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Debug)] pub struct TypeDefBitSequence { /// The type implementing [`bitvec::store::BitStore`]. diff --git a/src/ty/path.rs b/src/ty/path.rs index 872a5774..574e83c7 100644 --- a/src/ty/path.rs +++ b/src/ty/path.rs @@ -56,6 +56,7 @@ use serde::{ )] #[cfg_attr(feature = "serde", serde(transparent))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Encode)] pub struct Path { /// The segments of the namespace. diff --git a/src/ty/variant.rs b/src/ty/variant.rs index a7f72de4..a647018e 100644 --- a/src/ty/variant.rs +++ b/src/ty/variant.rs @@ -81,6 +81,7 @@ use serde::{ )] #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, From, Encode)] pub struct TypeDefVariant { /// The variants of a variant type @@ -154,6 +155,7 @@ where )) )] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Encode)] pub struct Variant { /// The name of the variant.