From aa575baeb7ebf36ff76319d494433bdeb8d8cc83 Mon Sep 17 00:00:00 2001 From: Nikita Strygin Date: Fri, 28 Jul 2023 17:00:07 +0300 Subject: [PATCH] [test]: Add a test for #[schema(transparent)] attribute and for JSON representation of schema Signed-off-by: Nikita Strygin --- Cargo.lock | 1 + Cargo.toml | 1 + macro/derive/Cargo.toml | 2 +- schema/Cargo.toml | 5 +- schema/derive/Cargo.toml | 2 +- .../tests/ui_pass/derive_into_schema.rs | 1 - schema/tests/schema_json.rs | 239 ++++++++++++++++++ schema/tests/transparent_types.rs | 77 ++++++ 8 files changed, 323 insertions(+), 5 deletions(-) create mode 100644 schema/tests/schema_json.rs create mode 100644 schema/tests/transparent_types.rs diff --git a/Cargo.lock b/Cargo.lock index 99e121dac36..53fe30e3d5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3370,6 +3370,7 @@ dependencies = [ "iroha_schema_derive", "parity-scale-codec", "serde", + "serde_json", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 59a339e8b25..d3be3e4c911 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,6 +74,7 @@ bytes = "1.4.0" vergen = { version = "8.1.1", default-features = false } trybuild = "1.0.73" +impls = "1.0.3" base64 = { version = "0.13.1", default-features = false } hex = { version = "0.4.3", default-features = false } diff --git a/macro/derive/Cargo.toml b/macro/derive/Cargo.toml index 0b2643ad140..e7dd8d06e2a 100644 --- a/macro/derive/Cargo.toml +++ b/macro/derive/Cargo.toml @@ -21,4 +21,4 @@ darling = { workspace = true } iroha_macro = { workspace = true } trybuild = { workspace = true } -impls = "1.0.3" +impls = { workspace = true } diff --git a/schema/Cargo.toml b/schema/Cargo.toml index df9e29954ce..d696b5c0426 100644 --- a/schema/Cargo.toml +++ b/schema/Cargo.toml @@ -14,5 +14,6 @@ serde = { workspace = true, features = ["derive", "alloc"] } fixnum = { workspace = true, features = ["i64"] } [dev-dependencies] -parity-scale-codec = { version = "3.2.1", default-features = false, features = ["derive", "full"] } -impls = "1.0.3" +parity-scale-codec = { workspace = true, default-features = false, features = ["derive", "full"] } +impls = { workspace = true } +serde_json = { workspace = true, features = ["std"] } diff --git a/schema/derive/Cargo.toml b/schema/derive/Cargo.toml index 9eaba49b093..e57a88b05f1 100644 --- a/schema/derive/Cargo.toml +++ b/schema/derive/Cargo.toml @@ -18,6 +18,6 @@ manyhow = { workspace = true, features = ["syn1", "darling"] } darling = { workspace = true } [dev-dependencies] -iroha_schema = { workspace = true} +iroha_schema = { workspace = true } trybuild = { workspace = true } diff --git a/schema/derive/tests/ui_pass/derive_into_schema.rs b/schema/derive/tests/ui_pass/derive_into_schema.rs index a191d11734f..576fb77f5ce 100644 --- a/schema/derive/tests/ui_pass/derive_into_schema.rs +++ b/schema/derive/tests/ui_pass/derive_into_schema.rs @@ -37,7 +37,6 @@ pub struct FakeString {} #[derive(IntoSchema)] pub enum Enum { Zero, - // how does it work? We don't allow discriminants, but still try to parse them? One, #[codec(index = 42)] FortyTwo, diff --git a/schema/tests/schema_json.rs b/schema/tests/schema_json.rs new file mode 100644 index 00000000000..1fc25ef131d --- /dev/null +++ b/schema/tests/schema_json.rs @@ -0,0 +1,239 @@ +//! This test checks how the json-serialized schema looks like. + +#![allow(dead_code)] +#![allow(unused_tuple_struct_fields)] + +use iroha_schema::IntoSchema; +use serde_json::json; + +macro_rules! check_schema_impl { + ($ty:ident, $type_name:ident, $json:tt) => {{ + assert_eq!( + $ty::type_name(), + stringify!($type_name), + "Type name of {} is not equal to the expected one", + stringify!($ty) + ); + let __schema = serde_json::value::to_value(&$ty::schema()) + .expect("Failed to serialize schema to JSON"); + // println!("{}: {}", stringify!($ty), __schema); + assert_eq!( + __schema.get(&$ty::type_name()).unwrap().clone(), + json!($json), + "Schema of {} is not equal to the expected one", + stringify!($ty) + ); + }}; +} + +/// It expects to have three parameters: a type definition item, an expected schema type name and a JSON schema. +/// +/// The json is passed to the `serde_json::json!` macro, so it can be a string, an array or an object. +/// +/// Only the schema of the type itself is checked, not the schema of its fields. +/// +/// NOTE: this macro doesn't support generics. +macro_rules! check_schema { + ($(#[$($meta:tt)*])* struct $ty:ident, $type_name:ident, $json:tt) => {{ + #[derive(IntoSchema)] + $(#[$($meta)*])* + struct $ty; + check_schema_impl!($ty, $type_name, $json); + }}; + ($(#[$($meta:tt)*])* struct $ty:ident ($($body:tt)*), $type_name:ident, $json:tt) => {{ + #[derive(IntoSchema)] + $(#[$($meta)*])* + struct $ty($($body)*); + check_schema_impl!($ty, $type_name, $json); + }}; + ($(#[$($meta:tt)*])* struct $ty:ident {$($body:tt)*}, $type_name:ident, $json:tt) => {{ + #[derive(IntoSchema)] + $(#[$($meta)*])* + struct $ty {$($body)*} + check_schema_impl!($ty, $type_name, $json); + }}; + ($(#[$($meta:tt)*])* enum $ty:ident {$($body:tt)*}, $type_name:ident, $json:tt) => {{ + #[derive(IntoSchema)] + $(#[$($meta)*])* + enum $ty {$($body)*} + check_schema_impl!($ty, $type_name, $json); + }}; +} + +#[test] +fn test_struct() { + check_schema!( + struct EmptyNamedStruct {}, + EmptyNamedStruct, + {"Struct": []} + ); + + // this behaviour is weird... + check_schema!( + struct EmptyTupleStruct(), + EmptyTupleStruct, + null + ); + check_schema!( + struct UnitStruct, + UnitStruct, + null + ); + + check_schema!( + struct NormalStruct { + normal_field_1: u32, + normal_field_2: u32, + }, + NormalStruct, + {"Struct": [ + {"name": "normal_field_1", "type": "u32"}, + {"name": "normal_field_2", "type": "u32"} + ]} + ); + check_schema!( + struct NewtypeStruct(u32), + NewtypeStruct, + "u32" + ); + check_schema!( + struct TupleStruct(u32, u32), + TupleStruct, + {"Tuple": [ + "u32", "u32" + ]} + ); +} + +#[test] +fn test_struct_codec_attr() { + check_schema!( + struct SkipField { + #[codec(skip)] + skipped_field: u32, + normal_field: u32, + }, + SkipField, + {"Struct": [ + {"name": "normal_field", "type": "u32"} + ]} + ); + check_schema!( + struct CompactField { + #[codec(compact)] + compact_field: u32, + }, + CompactField, + {"Struct": [ + {"name": "compact_field", "type": "Compact"} + ]} + ); +} + +#[test] +fn test_transparent() { + check_schema!( + #[schema(transparent)] + struct TransparentStruct(u32), + u32, + {"Int": "FixedWidth"} + ); + check_schema!( + #[schema(transparent = "u32")] + struct TransparentStructExplicitInt { + a: u32, + b: i32, + }, + u32, + {"Int": "FixedWidth"} + ); + check_schema!( + #[schema(transparent = "String")] + struct TransparentStructExplicitString { + a: u32, + b: i32, + }, + String, + "String" + ); + check_schema!( + #[schema(transparent = "String")] + enum TransparentEnum { + Variant1, + Variant2, + }, + String, + "String" + ); +} + +#[test] +fn test_enum() { + check_schema!( + enum EmptyEnum {}, + EmptyEnum, + {"Enum": []} + ); + check_schema!( + enum DatalessEnum { + Variant1, + Variant2, + }, + DatalessEnum, + {"Enum": [ + {"discriminant": 0, "tag": "Variant1"}, + {"discriminant": 1, "tag": "Variant2"} + ]} + ); + check_schema!( + enum DataEnum { + Variant1(u32), + // these variants are not supported by the schema + //Variant2(u32, u32), + //Variant2 { a: u32, b: u32 }, + Variant3(String) + }, + DataEnum, + {"Enum": [ + {"discriminant": 0, "tag": "Variant1", "type": "u32"}, + {"discriminant": 1, "tag": "Variant3", "type": "String"} + ]} + ) +} + +#[test] +fn test_enum_codec_attr() { + check_schema!( + enum SkipEnum { + #[codec(skip)] + Variant1, + Variant2, + }, + SkipEnum, + {"Enum": [ + {"discriminant": 1, "tag": "Variant2"} + ]} + ); + check_schema!( + enum IndexEnum { + // ERROR: Fieldless enums with explicit discriminants are not allowed + // Variant1 = 12, + #[codec(index = 42)] + Variant2, + }, + IndexEnum, + {"Enum": [ + {"discriminant": 42, "tag": "Variant2"} + ]} + ); + check_schema!( + enum IndexDataEnum { + #[codec(index = 42)] + Variant2(u32), + }, + IndexDataEnum, + {"Enum": [ + {"discriminant": 42, "tag": "Variant2", "type": "u32"} + ]} + ) +} diff --git a/schema/tests/transparent_types.rs b/schema/tests/transparent_types.rs new file mode 100644 index 00000000000..afc96d16de5 --- /dev/null +++ b/schema/tests/transparent_types.rs @@ -0,0 +1,77 @@ +extern crate alloc; + +use core::any::TypeId; + +use iroha_schema::prelude::*; +use parity_scale_codec::{Decode, Encode}; + +/// This type tests transparent type inference +#[derive(Decode, Encode, IntoSchema)] +#[schema(transparent)] +struct TransparentStruct(u32); + +/// This type tests explicit transparent type (u32) +#[derive(Decode, Encode, IntoSchema)] +#[schema(transparent = "u32")] +struct TransparentStructExplicitInt { + a: u32, + b: i32, +} + +/// This type tests explicit transparent type (String) +#[derive(Decode, Encode, IntoSchema)] +#[schema(transparent = "String")] +struct TransparentStructExplicitString { + a: u32, + b: i32, +} + +/// This type tests transparent type being an enum +#[derive(Decode, Encode, IntoSchema)] +#[schema(transparent = "String")] +enum TransparentEnum { + Variant1, + Variant2, +} + +#[test] +fn transparent_types() { + use alloc::collections::BTreeMap; + + use IntMode::*; + use Metadata::*; + + let expected = [ + ( + TypeId::of::(), + ("String".to_string(), String), + ), + (TypeId::of::(), ("u32".to_string(), Int(FixedWidth))), + ( + TypeId::of::(), + ("u32".to_string(), Int(FixedWidth)), + ), + ( + TypeId::of::(), + ("u32".to_string(), Int(FixedWidth)), + ), + ( + TypeId::of::(), + ("String".to_string(), String), + ), + ( + TypeId::of::(), + ("String".to_string(), String), + ), + ] + .into_iter() + .collect::>(); + + let mut schema = MetaMap::new(); + TransparentStruct::update_schema_map(&mut schema); + TransparentStructExplicitInt::update_schema_map(&mut schema); + TransparentStructExplicitString::update_schema_map(&mut schema); + TransparentEnum::update_schema_map(&mut schema); + + assert_eq!(schema, expected); +}