Skip to content

Commit 9855d42

Browse files
committed
PoC Rust codegen
1 parent 30b0d91 commit 9855d42

File tree

5 files changed

+223
-27
lines changed

5 files changed

+223
-27
lines changed

packages/cw-schema-codegen/src/main.rs

Lines changed: 150 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ extern crate log;
66

77
use clap::{Parser, ValueEnum};
88
use std::{
9-
fs::File,
9+
borrow::Cow,
10+
fs::{self, File},
1011
io::{self, Write},
1112
path::PathBuf,
1213
};
1314

14-
#[derive(Clone, Copy, Debug, Default, ValueEnum)]
15+
#[derive(Clone, Copy, Debug, Default, PartialEq, ValueEnum)]
1516
pub enum Language {
1617
#[default]
1718
Rust,
@@ -48,6 +49,52 @@ impl Opts {
4849
}
4950
}
5051

52+
fn expand_node_name<'a>(
53+
schema: &'a cw_schema::SchemaV1,
54+
node: &'a cw_schema::Node,
55+
) -> Cow<'a, str> {
56+
match node.value {
57+
cw_schema::NodeType::Array { items } => {
58+
let items = &schema.definitions[items];
59+
format!("Vec<{}>", expand_node_name(schema, items)).into()
60+
}
61+
cw_schema::NodeType::Float => "f32".into(),
62+
cw_schema::NodeType::Double => "f64".into(),
63+
cw_schema::NodeType::Boolean => "bool".into(),
64+
cw_schema::NodeType::String => "String".into(),
65+
cw_schema::NodeType::Integer { signed, precision } => {
66+
let ty = if signed { "i" } else { "u" };
67+
format!("{ty}{precision}").into()
68+
}
69+
cw_schema::NodeType::Binary => "Vec<u8>".into(),
70+
cw_schema::NodeType::Optional { inner } => {
71+
let inner = &schema.definitions[inner];
72+
format!("Option<{}>", expand_node_name(schema, inner)).into()
73+
}
74+
cw_schema::NodeType::Struct(..) => node.name.as_ref().into(),
75+
cw_schema::NodeType::Tuple { ref items } => {
76+
let items = items
77+
.iter()
78+
.map(|item| expand_node_name(schema, &schema.definitions[*item]))
79+
.collect::<Vec<_>>()
80+
.join(", ");
81+
82+
format!("({})", items).into()
83+
}
84+
cw_schema::NodeType::Enum { .. } => node.name.as_ref().into(),
85+
_ => todo!(),
86+
}
87+
}
88+
89+
fn prepare_docs(desc: Option<&str>) -> Cow<'_, [Cow<'_, str>]> {
90+
desc.map(|desc| {
91+
desc.lines()
92+
.map(|line| line.replace('"', "\\\"").into())
93+
.collect()
94+
})
95+
.unwrap_or(Cow::Borrowed(&[]))
96+
}
97+
5198
fn main() -> anyhow::Result<()> {
5299
simple_logger::SimpleLogger::new()
53100
.without_timestamps()
@@ -59,11 +106,111 @@ fn main() -> anyhow::Result<()> {
59106
opts.language, opts.file
60107
);
61108

62-
let schema = std::fs::read_to_string(&opts.file)?;
109+
ensure!(opts.file.exists(), "Schema file does not exist");
110+
ensure!(
111+
opts.language == Language::Rust,
112+
"Only Rust code generation is supported at the moment"
113+
);
114+
115+
let schema = fs::read_to_string(&opts.file)?;
63116
let schema: cw_schema::Schema = serde_json::from_str(&schema)?;
64117
let cw_schema::Schema::V1(schema) = schema else {
65118
bail!("Unsupported schema version");
66119
};
67120

121+
let mut output = opts.output()?;
122+
123+
schema.definitions.iter().for_each(|node| {
124+
debug!("Processing node: {node:?}");
125+
126+
match node.value {
127+
cw_schema::NodeType::Struct(ref sty) => {
128+
let structt = cw_schema_codegen::rust::StructTemplate {
129+
name: &node.name,
130+
docs: prepare_docs(node.description.as_deref()),
131+
ty: match sty {
132+
cw_schema::StructType::Unit => cw_schema_codegen::rust::TypeTemplate::Unit,
133+
cw_schema::StructType::Named { ref properties } => {
134+
cw_schema_codegen::rust::TypeTemplate::Named {
135+
fields: properties
136+
.iter()
137+
.map(|(name, prop)| {
138+
let ty = expand_node_name(
139+
&schema,
140+
&schema.definitions[prop.value],
141+
);
142+
cw_schema_codegen::rust::FieldTemplate {
143+
name: Cow::Borrowed(name),
144+
docs: prepare_docs(prop.description.as_deref()),
145+
ty,
146+
}
147+
})
148+
.collect(),
149+
}
150+
}
151+
_ => unreachable!(),
152+
},
153+
};
154+
155+
writeln!(output, "{structt}").unwrap();
156+
}
157+
cw_schema::NodeType::Enum { ref cases, .. } => {
158+
let enumm = cw_schema_codegen::rust::EnumTemplate {
159+
name: &node.name,
160+
docs: prepare_docs(node.description.as_deref()),
161+
variants: cases
162+
.iter()
163+
.map(
164+
|(name, case)| cw_schema_codegen::rust::EnumVariantTemplate {
165+
name,
166+
docs: prepare_docs(case.description.as_deref()),
167+
ty: match case.value {
168+
cw_schema::EnumValue::Unit => {
169+
cw_schema_codegen::rust::TypeTemplate::Unit
170+
}
171+
cw_schema::EnumValue::Tuple { ref items } => {
172+
let items = items
173+
.iter()
174+
.map(|item| {
175+
let node = &schema.definitions[*item];
176+
expand_node_name(&schema, node)
177+
})
178+
.collect();
179+
180+
cw_schema_codegen::rust::TypeTemplate::Tuple(items)
181+
}
182+
cw_schema::EnumValue::Named { ref properties, .. } => {
183+
cw_schema_codegen::rust::TypeTemplate::Named {
184+
fields: properties
185+
.iter()
186+
.map(|(name, prop)| {
187+
let ty = expand_node_name(
188+
&schema,
189+
&schema.definitions[prop.value],
190+
);
191+
cw_schema_codegen::rust::FieldTemplate {
192+
name: Cow::Borrowed(name),
193+
docs: prepare_docs(
194+
prop.description.as_deref(),
195+
),
196+
ty,
197+
}
198+
})
199+
.collect(),
200+
}
201+
}
202+
_ => unreachable!(),
203+
},
204+
},
205+
)
206+
.collect(),
207+
};
208+
209+
writeln!(output, "{enumm}").unwrap();
210+
}
211+
_ => (),
212+
}
213+
});
214+
68215
Ok(())
69216
}
Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,41 @@
11
use askama::Template;
2+
use std::borrow::Cow;
23

4+
#[derive(Clone)]
35
pub struct EnumVariantTemplate<'a> {
46
pub name: &'a str,
7+
pub docs: Cow<'a, [Cow<'a, str>]>,
58
pub ty: TypeTemplate<'a>,
69
}
710

811
#[derive(Template)]
912
#[template(escape = "none", path = "rust/enum.tpl.rs")]
1013
pub struct EnumTemplate<'a> {
1114
pub name: &'a str,
12-
pub variants: &'a [EnumVariantTemplate<'a>],
15+
pub docs: Cow<'a, [Cow<'a, str>]>,
16+
pub variants: Cow<'a, [EnumVariantTemplate<'a>]>,
1317
}
1418

19+
#[derive(Clone)]
1520
pub struct FieldTemplate<'a> {
16-
pub name: &'a str,
17-
pub ty: &'a str,
21+
pub name: Cow<'a, str>,
22+
pub docs: Cow<'a, [Cow<'a, str>]>,
23+
pub ty: Cow<'a, str>,
1824
}
1925

26+
#[derive(Clone)]
2027
pub enum TypeTemplate<'a> {
2128
Unit,
22-
Tuple(&'a [&'a str]),
23-
Named { fields: &'a [FieldTemplate<'a>] },
29+
Tuple(Cow<'a, [Cow<'a, str>]>),
30+
Named {
31+
fields: Cow<'a, [FieldTemplate<'a>]>,
32+
},
2433
}
2534

2635
#[derive(Template)]
2736
#[template(escape = "none", path = "rust/struct.tpl.rs")]
2837
pub struct StructTemplate<'a> {
2938
pub name: &'a str,
39+
pub docs: Cow<'a, [Cow<'a, str>]>,
3040
pub ty: TypeTemplate<'a>,
3141
}

packages/cw-schema-codegen/templates/rust/enum.tpl.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1+
{% for doc in docs %}
2+
#[doc = "{{ doc }}"]
3+
{% endfor %}
4+
15
pub enum {{ name }} {
26
{% for variant in variants %}
7+
{% for doc in variant.docs %}
8+
#[doc = "{{ doc }}"]
9+
{% endfor %}
10+
311
{{ variant.name }}
412
{% match variant.ty %}
513
{% when TypeTemplate::Unit %}
@@ -12,6 +20,10 @@ pub enum {{ name }} {
1220
{% when TypeTemplate::Named with { fields } %}
1321
{
1422
{% for field in fields %}
23+
{% for doc in field.docs %}
24+
#[doc = "{{ doc }}"]
25+
{% endfor %}
26+
1527
{{ field.name }}: {{ field.ty }},
1628
{% endfor %}
1729
}

packages/cw-schema-codegen/templates/rust/struct.tpl.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
{% for doc in docs %}
2+
#[doc = "{{ doc }}"]
3+
{% endfor %}
4+
15
pub struct {{ name }}
26

37
{% match ty %}
@@ -12,6 +16,10 @@ pub struct {{ name }}
1216
{% when TypeTemplate::Named with { fields } %}
1317
{
1418
{% for field in fields %}
19+
{% for doc in field.docs %}
20+
#[doc = "{{ doc }}"]
21+
{% endfor %}
22+
1523
{{ field.name }}: {{ field.ty }},
1624
{% endfor %}
1725
}

0 commit comments

Comments
 (0)