Skip to content

Commit 777aca3

Browse files
author
Tomasz Kulik
committed
chore: Add tests and validate schemas
1 parent 2a737c8 commit 777aca3

File tree

6 files changed

+217
-82
lines changed

6 files changed

+217
-82
lines changed

packages/cw-schema-codegen/playground/playground.py

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class __InnerStruct(BaseModel):
2424
a: 'SomeEnum'
2525
Field5: __InnerStruct
2626

27-
root: Union[Field1, Field2, Field3, Field4, Field5]
27+
root: Union[Field1, Field2, Field3, Field4, Field5,]
2828

2929

3030
class UnitStructure(RootModel):
@@ -46,21 +46,14 @@ class NamedStructure(BaseModel):
4646
### TESTS:
4747
###
4848

49-
for (index, input) in enumerate(sys.stdin):
50-
input = input.rstrip()
51-
try:
52-
if index < 5:
53-
deserialized = SomeEnum.model_validate_json(input)
54-
elif index == 5:
55-
deserialized = UnitStructure.model_validate_json(input)
56-
elif index == 6:
57-
deserialized = TupleStructure.model_validate_json(input)
58-
else:
59-
deserialized = NamedStructure.model_validate_json(input)
60-
except:
61-
raise(Exception(f"This json can't be deserialized: {input}"))
62-
serialized = deserialized.model_dump_json()
63-
print(serialized)
49+
print(SomeEnum.model_validate_json(sys.stdin.readline().rstrip()).model_dump_json())
50+
print(SomeEnum.model_validate_json(sys.stdin.readline().rstrip()).model_dump_json())
51+
print(SomeEnum.model_validate_json(sys.stdin.readline().rstrip()).model_dump_json())
52+
print(SomeEnum.model_validate_json(sys.stdin.readline().rstrip()).model_dump_json())
53+
print(SomeEnum.model_validate_json(sys.stdin.readline().rstrip()).model_dump_json())
54+
print(UnitStructure.model_validate_json(sys.stdin.readline().rstrip()).model_dump_json())
55+
print(TupleStructure.model_validate_json(sys.stdin.readline().rstrip()).model_dump_json())
56+
print(NamedStructure.model_validate_json(sys.stdin.readline().rstrip()).model_dump_json())
6457

6558

6659
# def handle_msg(json):

packages/cw-schema-codegen/src/python/mod.rs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use self::template::{
22
EnumTemplate, EnumVariantTemplate, FieldTemplate, StructTemplate, TypeTemplate,
33
};
4-
use heck::ToPascalCase;
54
use std::{borrow::Cow, io};
65

76
pub mod template;
@@ -15,15 +14,15 @@ fn expand_node_name<'a>(
1514
let items = &schema.definitions[items];
1615
format!("{}[]", expand_node_name(schema, items)).into()
1716
}
18-
cw_schema::NodeType::Float => "number".into(),
19-
cw_schema::NodeType::Double => "number".into(),
20-
cw_schema::NodeType::Boolean => "boolean".into(),
21-
cw_schema::NodeType::String => "string".into(),
22-
cw_schema::NodeType::Integer { .. } => "string".into(),
23-
cw_schema::NodeType::Binary => "Uint8Array".into(),
17+
cw_schema::NodeType::Float => "float".into(),
18+
cw_schema::NodeType::Double => "float".into(),
19+
cw_schema::NodeType::Boolean => "bool".into(),
20+
cw_schema::NodeType::String => "str".into(),
21+
cw_schema::NodeType::Integer { .. } => "int".into(),
22+
cw_schema::NodeType::Binary => "bytes".into(),
2423
cw_schema::NodeType::Optional { inner } => {
2524
let inner = &schema.definitions[inner];
26-
format!("{} | null", expand_node_name(schema, inner)).into()
25+
format!("typing.Optional[{}]", expand_node_name(schema, inner)).into()
2726
}
2827
cw_schema::NodeType::Struct(..) => node.name.as_ref().into(),
2928
cw_schema::NodeType::Tuple { ref items } => {
@@ -37,13 +36,13 @@ fn expand_node_name<'a>(
3736
}
3837
cw_schema::NodeType::Enum { .. } => node.name.as_ref().into(),
3938

40-
cw_schema::NodeType::Decimal { .. } => "string".into(),
41-
cw_schema::NodeType::Address => "string".into(),
39+
cw_schema::NodeType::Decimal { .. } => "decimal.Decimal".into(),
40+
cw_schema::NodeType::Address => "str".into(),
4241
cw_schema::NodeType::Checksum => todo!(),
4342
cw_schema::NodeType::HexBinary => todo!(),
4443
cw_schema::NodeType::Timestamp => todo!(),
45-
cw_schema::NodeType::Unit => Cow::Borrowed("void"),
46-
_ => todo!()
44+
cw_schema::NodeType::Unit => "None".into(),
45+
_ => todo!(),
4746
}
4847
}
4948

@@ -83,7 +82,7 @@ where
8382
.map(|item| expand_node_name(schema, &schema.definitions[*item]))
8483
.collect(),
8584
),
86-
_ => todo!()
85+
_ => todo!(),
8786
},
8887
};
8988

@@ -125,7 +124,7 @@ where
125124
.collect(),
126125
}
127126
}
128-
_ => todo!()
127+
_ => todo!(),
129128
},
130129
})
131130
.collect(),
Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# This code is @generated by cw-schema-codegen. Do not modify this manually.
22

33
import typing
4+
import decimal
45
from pydantic import BaseModel, RootModel
56

67
class {{ name }}(RootModel):
@@ -15,24 +16,26 @@ class {{ variant.name }}(RootModel):
1516
"""{% for doc in variant.docs %}
1617
{{ doc }}
1718
{% endfor %}"""
18-
root: None
19+
root: typing.Literal['{{ variant.name }}']
1920
{% when TypeTemplate::Tuple with (types) %}
2021
class {{ variant.name }}(BaseModel):
2122
"""{% for doc in variant.docs %}
2223
{{ doc }}
2324
{% endfor %}"""
2425
{{ variant.name }}: typing.Tuple[{{ types|join(", ") }}]
2526
{% when TypeTemplate::Named with { fields } %}
26-
class __Inner:
27-
"""{% for doc in variant.docs %}
28-
{{ doc }}
29-
{% endfor %}"""
30-
{% for field in fields %}
31-
{{ field.name }}: {{ field.ty }}
32-
"""{% for doc in field.docs %}
33-
# {{ doc }}
34-
{% endfor %}"""
35-
{% endfor %}
27+
class {{ variant.name }}(BaseModel):
28+
class __Inner(BaseModel):
29+
"""{% for doc in variant.docs %}
30+
{{ doc }}
31+
{% endfor %}"""
32+
{% for field in fields %}
33+
{{ field.name }}: {{ field.ty }}
34+
"""{% for doc in field.docs %}
35+
{{ doc }}
36+
{% endfor %}"""
37+
{% endfor %}
3638
{{ variant.name }}: __Inner
3739
{% endmatch %}
38-
{% endfor %}
40+
{% endfor %}
41+
root: typing.Union[ {% for variant in variants %} {{ variant.name }}, {% endfor %} ]

packages/cw-schema-codegen/templates/python/struct.tpl.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# This code is @generated by cw-schema-codegen. Do not modify this manually.
22

33
import typing
4+
import decimal
45
from pydantic import BaseModel, RootModel
56

67

@@ -10,7 +11,7 @@ class {{ name }}(RootModel):
1011
'''{% for doc in docs %}
1112
{{ doc }}
1213
{% endfor %}'''
13-
root: None
14+
root: None
1415
{% when TypeTemplate::Tuple with (types) %}
1516
class {{ name }}(RootModel):
1617
'''{% for doc in docs %}
Lines changed: 151 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,156 @@
1-
use std::borrow::Cow;
1+
use cw_schema::Schemaifier;
2+
use serde::{Deserialize, Serialize};
3+
use std::io::Write;
24

3-
use askama::Template;
4-
use cw_schema_codegen::python::template::{
5-
EnumTemplate, EnumVariantTemplate, FieldTemplate, StructTemplate, TypeTemplate,
6-
};
5+
#[derive(Schemaifier, Serialize, Deserialize)]
6+
pub enum SomeEnum {
7+
Field1,
8+
Field2(u32, u32),
9+
Field3 { a: String, b: u32 },
10+
// Field4(Box<SomeEnum>), // TODO tkulik: Do we want to support Box<T> ?
11+
// Field5 { a: Box<SomeEnum> },
12+
}
13+
14+
#[derive(Schemaifier, Serialize, Deserialize)]
15+
pub struct UnitStructure;
16+
17+
#[derive(Schemaifier, Serialize, Deserialize)]
18+
pub struct TupleStructure(u32, String, u128);
19+
20+
#[derive(Schemaifier, Serialize, Deserialize)]
21+
pub struct NamedStructure {
22+
a: String,
23+
b: u8,
24+
c: SomeEnum,
25+
}
726

827
#[test]
928
fn simple_enum() {
10-
let tpl = EnumTemplate {
11-
name: Cow::Borrowed("Simple"),
12-
docs: Cow::Borrowed(&[Cow::Borrowed("Simple enum")]),
13-
variants: Cow::Borrowed(&[
14-
EnumVariantTemplate {
15-
name: Cow::Borrowed("One"),
16-
docs: Cow::Borrowed(&[Cow::Borrowed("One variant")]),
17-
ty: TypeTemplate::Unit,
18-
},
19-
EnumVariantTemplate {
20-
name: Cow::Borrowed("Two"),
21-
docs: Cow::Borrowed(&[Cow::Borrowed("Two variant")]),
22-
ty: TypeTemplate::Unit,
23-
},
24-
]),
25-
};
26-
27-
let rendered = tpl.render().unwrap();
28-
insta::assert_snapshot!(rendered);
29+
// generate the schemas for each of the above types
30+
let schemas = [
31+
cw_schema::schema_of::<SomeEnum>(),
32+
cw_schema::schema_of::<UnitStructure>(),
33+
cw_schema::schema_of::<TupleStructure>(),
34+
cw_schema::schema_of::<NamedStructure>(),
35+
];
36+
37+
// run the codegen to typescript
38+
for schema in schemas {
39+
let cw_schema::Schema::V1(schema) = schema else {
40+
panic!();
41+
};
42+
43+
let output = schema
44+
.definitions
45+
.iter()
46+
.map(|node| {
47+
let mut buf = Vec::new();
48+
cw_schema_codegen::python::process_node(&mut buf, &schema, node).unwrap();
49+
String::from_utf8(buf).unwrap()
50+
})
51+
.collect::<String>();
52+
53+
insta::assert_snapshot!(output);
54+
}
55+
}
56+
57+
macro_rules! validator {
58+
($typ:ty) => {{
59+
let a: Box<dyn FnOnce(&str) -> ()> = Box::new(|output| {
60+
serde_json::from_str::<$typ>(output).unwrap();
61+
});
62+
a
63+
}};
64+
}
65+
66+
#[test]
67+
fn assert_validity() {
68+
let schemas = [
69+
(
70+
"SomeEnum",
71+
cw_schema::schema_of::<SomeEnum>(),
72+
serde_json::to_string(&SomeEnum::Field1).unwrap(),
73+
validator!(SomeEnum),
74+
),
75+
(
76+
"SomeEnum",
77+
cw_schema::schema_of::<SomeEnum>(),
78+
serde_json::to_string(&SomeEnum::Field2(10, 23)).unwrap(),
79+
validator!(SomeEnum),
80+
),
81+
(
82+
"SomeEnum",
83+
cw_schema::schema_of::<SomeEnum>(),
84+
serde_json::to_string(&SomeEnum::Field3 {
85+
a: "sdf".to_string(),
86+
b: 12,
87+
})
88+
.unwrap(),
89+
validator!(SomeEnum),
90+
),
91+
(
92+
"UnitStructure",
93+
cw_schema::schema_of::<UnitStructure>(),
94+
serde_json::to_string(&UnitStructure {}).unwrap(),
95+
validator!(UnitStructure),
96+
),
97+
(
98+
"TupleStructure",
99+
cw_schema::schema_of::<TupleStructure>(),
100+
serde_json::to_string(&TupleStructure(10, "aasdf".to_string(), 2)).unwrap(),
101+
validator!(TupleStructure),
102+
),
103+
(
104+
"NamedStructure",
105+
cw_schema::schema_of::<NamedStructure>(),
106+
serde_json::to_string(&NamedStructure {
107+
a: "awer".to_string(),
108+
b: 4,
109+
c: SomeEnum::Field1,
110+
})
111+
.unwrap(),
112+
validator!(NamedStructure),
113+
),
114+
];
115+
116+
for (type_name, schema, example, validator) in schemas {
117+
let cw_schema::Schema::V1(schema) = schema else {
118+
unreachable!();
119+
};
120+
121+
let schema_output = schema
122+
.definitions
123+
.iter()
124+
.map(|node| {
125+
let mut buf = Vec::new();
126+
cw_schema_codegen::python::process_node(&mut buf, &schema, node).unwrap();
127+
String::from_utf8(buf).unwrap()
128+
})
129+
.collect::<String>();
130+
131+
let mut file = tempfile::NamedTempFile::with_suffix(".py").unwrap();
132+
file.write_all(schema_output.as_bytes()).unwrap();
133+
file.write(
134+
format!(
135+
"import sys; print({type_name}.model_validate_json('{example}').model_dump_json())"
136+
)
137+
.as_bytes(),
138+
)
139+
.unwrap();
140+
file.flush().unwrap();
141+
142+
let output = std::process::Command::new("python")
143+
.arg(file.path())
144+
.output()
145+
.unwrap();
146+
147+
assert!(
148+
output.status.success(),
149+
"stdout: {stdout}, stderr: {stderr}\n\n schema:\n {schema_output}",
150+
stdout = String::from_utf8_lossy(&output.stdout),
151+
stderr = String::from_utf8_lossy(&output.stderr),
152+
);
153+
154+
validator(&String::from_utf8_lossy(&output.stdout))
155+
}
29156
}

0 commit comments

Comments
 (0)