1use super::{field_name, to_atom};
2use crate::ast;
3
4use inflector::Inflector;
5use itertools::Itertools;
6
7pub fn generate_struct_and_enum_encoders(spec: &ast::Spec) -> String {
9 spec.iter()
10 .filter_map(|spec_item| match spec_item {
11 ast::SpecItem::StructDef(sdef) => {
12 let json_encoder = generate_struct_json_encoder(sdef);
13 let query_encoder = generate_struct_query_encoder(sdef);
14 Some(format!("{}\n\n\n{}", json_encoder, query_encoder))
15 }
16 ast::SpecItem::EnumDef(edef) => Some(generate_enum_encoder(edef)),
17 ast::SpecItem::ServiceDef(_) => None,
18 })
19 .join("\n\n\n")
20}
21
22fn generate_struct_json_encoder(sdef: &ast::StructDef) -> String {
23 let ns = "";
24 format!(
25 "{encoder_name} : {type_name} -> E.Value\n{encoder_name} obj =\n E.object\n [ {fields}\n ]",
26 encoder_name = struct_or_enum_encoder_name(&sdef.name, ns),
27 type_name = sdef.name,
28 fields = sdef.fields.iter().map(|f| generate_field_json_encoder(f, ns)).join("\n , "),
29 )
30}
31
32fn generate_struct_query_encoder(sdef: &ast::StructDef) -> String {
33 let ns = "";
34 format!(
35 "{encoder_name} : {type_name} -> List Url.Builder.QueryParameter\n{encoder_name} obj =\n [ {fields}\n ]",
36 encoder_name = query_struct_encoder_name(&sdef.name, ns),
37 type_name = sdef.name,
38 fields = sdef.fields.iter().map(|f| generate_field_query_encoder(f, ns)).join("\n , "),
39 )
40}
41
42fn generate_enum_encoder(edef: &ast::EnumDef) -> String {
43 let ns = "";
44
45 format!(
46 "{encoder_name} : {type_name} -> E.Value\n{encoder_name} v =\n case v of\n {variants}",
47 encoder_name = struct_or_enum_encoder_name(&edef.name, ns),
48 type_name = edef.name,
49 variants = edef
50 .variants
51 .iter()
52 .map(|v| generate_variant_encoder_branch(v, ns))
53 .join("\n "),
54 )
55}
56
57fn generate_field_json_encoder(field: &ast::FieldNode, ns: &str) -> String {
58 format!(
59 "(\"{name}\", {value_encoder} obj.{field_name})",
60 name = field.pair.name,
61 field_name = field_name(&field.pair.name),
62 value_encoder = generate_type_json_encoder(&field.pair.type_ident, ns)
63 )
64}
65
66fn generate_field_query_encoder(field: &ast::FieldNode, ns: &str) -> String {
67 match field.pair.type_ident {
69 ast::TypeIdent::BuiltIn(ast::AtomType::Str) => format!(
70 "Url.Builder.string \"{name}\" obj.{field_name}",
71 name = field.pair.name,
72 field_name = field_name(&field.pair.name)
73 ),
74 ast::TypeIdent::BuiltIn(ast::AtomType::Uuid) => format!(
75 "Url.Builder.string \"{name}\" (BuiltinUuid.encodeQuery obj.{field_name})",
76 name = field.pair.name,
77 field_name = field_name(&field.pair.name)
78 ),
79 ast::TypeIdent::BuiltIn(ast::AtomType::Bytes) => format!(
80 "Url.Builder.string \"{name}\" (BuiltinBytes.encodeQuery obj.{field_name})",
81 name = field.pair.name,
82 field_name = field_name(&field.pair.name)
83 ),
84 ast::TypeIdent::BuiltIn(ast::AtomType::I32)
85 | ast::TypeIdent::BuiltIn(ast::AtomType::U32)
86 | ast::TypeIdent::BuiltIn(ast::AtomType::U8) => format!(
87 "Url.Builder.int \"{name}\" obj.{field_name}",
88 name = field.pair.name,
89 field_name = field_name(&field.pair.name),
90 ),
91 _ => {
92 format!(
94 "obj.{field_name} |> {value_encoder} |> E.encode 4 |> Url.Builder.string \"{name}\"",
95 name = field.pair.name,
96 field_name = field_name(&field.pair.name),
97 value_encoder = generate_complex_type_query_encoder(&field.pair.type_ident, ns)
98 )
99 }
100 }
101}
102
103fn generate_variant_encoder_branch(variant: &ast::VariantDef, ns: &str) -> String {
104 match variant.variant_type {
105 ast::VariantType::Simple => format!("{name} -> E.string \"{name}\"", name = variant.name),
106 ast::VariantType::Tuple(ref tdef) => format!(
107 "{name} {field_names} -> E.object [ (\"{name}\", E.list identity [{field_encoders}]) ]",
108 name = variant.name,
109 field_names = (0..tdef.elements().len())
110 .map(|i| format!("x{}", i))
111 .join(" "),
112 field_encoders = tdef
113 .elements()
114 .iter()
115 .enumerate()
116 .map(|(idx, component)| format!(
117 "{} x{}",
118 generate_type_json_encoder(component, ns),
119 idx
120 ))
121 .join(", "),
122 ),
123 ast::VariantType::Struct(ref fields) => format!(
124 "{name} obj -> E.object [ (\"{name}\", E.object [{fields}]) ]",
125 name = variant.name,
126 fields = fields
127 .iter()
128 .map(|f| generate_field_json_encoder(f, ns))
129 .join(", "),
130 ),
131 ast::VariantType::Newtype(ref ty) => format!(
132 "{name} obj -> E.object [ (\"{name}\", {enc} obj) ]",
133 name = variant.name,
134 enc = generate_type_json_encoder(ty, ns),
135 ),
136 }
137}
138
139fn generate_type_encoder(
141 atom_encoder: &dyn Fn(&ast::AtomType, &str) -> String,
142 type_ident: &ast::TypeIdent,
143 ns: &str,
144) -> String {
145 match type_ident {
146 ast::TypeIdent::BuiltIn(atom) => atom_encoder(atom, ns),
147 ast::TypeIdent::List(inner) => {
148 format!("E.list {}", to_atom(generate_type_json_encoder(inner, ns)))
149 }
150 ast::TypeIdent::Option(inner) => format!(
151 "builtinEncodeMaybe {}",
152 to_atom(generate_type_json_encoder(inner, ns))
153 ),
154 ast::TypeIdent::Result(ok, err) => format!(
155 "builtinEncodeResult {} {}",
156 to_atom(generate_type_json_encoder(err, ns)),
157 to_atom(generate_type_json_encoder(ok, ns))
158 ),
159 ast::TypeIdent::Map(key, value) => {
160 assert_eq!(
161 generate_type_json_encoder(key, ns),
162 "E.string",
163 "can only encode string keys in maps"
164 );
165 format!(
166 "E.dict identity {}",
167 to_atom(generate_type_json_encoder(value, ns))
168 )
169 }
170 ast::TypeIdent::Tuple(tdef) => generate_tuple_encoder(tdef, ns),
171 ast::TypeIdent::UserDefined(ident) => struct_or_enum_encoder_name(ident, ns),
172 }
173}
174
175pub(crate) fn generate_type_json_encoder(type_ident: &ast::TypeIdent, ns: &str) -> String {
176 generate_type_encoder(&generate_atom_json_encoder, type_ident, ns)
177}
178
179fn generate_complex_type_query_encoder(type_ident: &ast::TypeIdent, ns: &str) -> String {
180 generate_type_encoder(&generate_atom_query_encoder, type_ident, ns)
181}
182
183pub(crate) fn generate_type_urlcomponent_encoder(type_ident: &ast::TypeIdent, ns: &str) -> String {
184 generate_type_encoder(&generate_atom_urlcomponent_encoder, type_ident, ns)
185}
186
187fn generate_atom_json_encoder(atom: &ast::AtomType, ns: &str) -> String {
188 match atom {
189 ast::AtomType::Empty => "(_ -> E.null)".to_owned(),
190 ast::AtomType::Str => "E.string".to_owned(),
191 ast::AtomType::I32 => "E.int".to_owned(),
192 ast::AtomType::U32 => "E.int".to_owned(),
193 ast::AtomType::U8 => "E.int".to_owned(),
194 ast::AtomType::F64 => "E.float".to_owned(),
195 ast::AtomType::Bool => "E.bool".to_owned(),
196 ast::AtomType::DateTime => format!("{}builtinEncodeIso8601", ns),
197 ast::AtomType::Date => format!("{}builtinEncodeDate", ns),
198 ast::AtomType::Uuid => "BuiltinUuid.encode".to_owned(),
199 ast::AtomType::Bytes => "BuiltinBytes.encode".to_owned(),
200 }
201}
202
203fn generate_atom_query_encoder(atom: &ast::AtomType, ns: &str) -> String {
204 match atom {
205 ast::AtomType::Empty => "E.null".to_owned(),
206 ast::AtomType::Str => "Url.Builder.string".to_owned(),
207 ast::AtomType::Uuid => "Url.Builder.uuid".to_owned(),
208 ast::AtomType::Bytes => "Url.Builder.bytes".to_owned(),
209 ast::AtomType::I32 | ast::AtomType::U32 | ast::AtomType::U8 => "Url.Builder.int".to_owned(),
210 ast::AtomType::F64 => "E.float".to_owned(),
211 ast::AtomType::Bool => "E.bool".to_owned(),
212 ast::AtomType::DateTime => format!("{}builtinEncodeIso8601", ns),
213 ast::AtomType::Date => format!("{}builtinEncodeDate", ns),
214 }
215}
216
217fn generate_atom_urlcomponent_encoder(atom: &ast::AtomType, ns: &str) -> String {
218 match atom {
219 ast::AtomType::Empty => unimplemented!(),
220 ast::AtomType::Str => "identity".to_owned(),
221 ast::AtomType::I32 | ast::AtomType::U32 | ast::AtomType::U8 => "String.fromInt".to_owned(),
222 ast::AtomType::F64 => "String.fromFloat".to_owned(),
223 ast::AtomType::Bool => "String.fromBool".to_owned(),
224 ast::AtomType::DateTime => format!("{}builtinEncodeIso8601", ns),
225 ast::AtomType::Date => format!("{}builtinEncodeDate", ns),
226 ast::AtomType::Uuid => "BuiltinUuid.encodeUrlcomponent".to_owned(),
227 ast::AtomType::Bytes => "BuiltinBytes.encodeUrlcomponent".to_owned(),
228 }
229}
230
231fn generate_tuple_encoder(tdef: &ast::TupleDef, ns: &str) -> String {
232 format!(
233 "\\({field_names}) -> E.list identity [ {encode_values} ]",
234 field_names = (0..tdef.elements().len())
235 .map(|i| format!("x{}", i))
236 .join(", "),
237 encode_values = tdef
238 .elements()
239 .iter()
240 .enumerate()
241 .map(|(idx, component)| format!(
242 "{} x{}",
243 generate_type_json_encoder(component, ns),
244 idx
245 ))
246 .join(", "),
247 )
248}
249
250pub(crate) fn struct_or_enum_encoder_name(ident: &str, ns: &str) -> String {
252 format!("{}encode{}", ns, ident.to_pascal_case())
253}
254
255pub(crate) fn query_encoder(ident: &ast::TypeIdent, ns: &str) -> String {
256 if let ast::TypeIdent::UserDefined(query_ty_name) = ident {
258 query_struct_encoder_name(query_ty_name, ns)
259 } else {
260 panic!("query MUST be a user defined struct");
261 }
262}
263
264pub(crate) fn query_struct_encoder_name(ident: &str, ns: &str) -> String {
265 format!("{}buildQuery{}", ns, ident.to_pascal_case())
266}