1use std::{fmt, fmt::Write};
2
3use indenter::indented;
4use indoc::writedoc;
5
6mod validate;
7
8use super::tabify;
9use validate::UnionValidate;
10
11pub use crate::types::*;
12
13pub enum GoType<'a> {
18 Struct(&'a Struct),
20 NewType(&'a NewType),
22 Alias(&'a Alias),
24 Enum(&'a Enum),
26 Union(&'a Union),
28}
29
30impl fmt::Display for GoType<'_> {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
32 let f = &mut tabify::tabify(f);
33 match self {
34 GoType::Struct(details) => {
35 writeln!(f, "type {} struct {{", details.name)?;
36 for field in &details.fields {
37 writeln!(indented(f), "{}", GoField(field))?;
38 }
39 writeln!(f, "}}")?;
40 }
41 GoType::NewType(details) => {
42 writeln!(f, "type {} {}", details.name, details.inner.go_type())?;
43 }
44 GoType::Alias(details) => {
45 writeln!(f, "type {} {}", details.name, details.inner.go_type())?;
46 }
47 GoType::Enum(details) => {
48 writeln!(f, "type {} string\n", details.name)?;
49 writeln!(f, "const (")?;
50 for variant in &details.variants {
51 writeln!(
52 indented(f),
53 "{}{} {} = \"{}\"",
54 details.name,
55 variant.name,
56 details.name,
57 variant.serialized_name
58 )?;
59 }
60 writeln!(f, ")")?;
61 }
62 GoType::Union(details) => {
63 writeln!(f, "type {} struct {{", details.name)?;
64 for variant in &details.variants {
65 writeln!(
66 indented(f),
67 "{} *{}",
68 variant.go_name(),
69 variant.ty.go_type()
70 )?;
71 }
72 writeln!(f, "}}\n")?;
73 write!(f, "{}", UnionMarshal(details))?;
74 write!(f, "{}", UnionUnmarshal(details))?;
75 write!(f, "{}", UnionValidate(details))?;
76 }
77 }
78
79 Ok(())
80 }
81}
82
83pub struct GoField<'a>(&'a Field);
84
85impl fmt::Display for GoField<'_> {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
87 let details = self.0;
88 write!(
89 f,
90 r#"{} {} `json:"{}"`"#,
91 to_pascal_case(&details.name),
92 details.ty.go_type(),
93 details.serialized_name
94 )
95 }
96}
97
98impl FieldType {
99 fn go_type(&self) -> String {
100 match self {
101 FieldType::Named(type_ref) => type_ref.name().to_string(),
102 FieldType::Optional(inner) => format!("*{}", inner.go_type()),
103 FieldType::List(inner) => format!("[]{}", inner.go_type()),
104 FieldType::Map { key, value } => format!("map[{}]{}", key.go_type(), value.go_type()),
105 FieldType::Primitive(Primitive::String) => "string".to_string(),
106 FieldType::Primitive(Primitive::Float) => "float64".to_string(),
107 FieldType::Primitive(Primitive::Int) => "int".to_string(),
108 FieldType::Primitive(Primitive::Bool) => "bool".to_string(),
109 FieldType::Primitive(Primitive::Time) => "time.Time".to_string(),
110 }
111 }
112}
113
114struct UnionMarshal<'a>(&'a Union);
115struct UnionUnmarshal<'a>(&'a Union);
116
117impl fmt::Display for UnionMarshal<'_> {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 let details = self.0;
120 writedoc!(
121 f,
122 r#"
123 func (self {}) MarshalJSON() ([]byte, error) {{
124 if err := self.Validate(); err != nil {{
125 return nil, fmt.Errorf("Validate Failed: %w", err)
126 }}
127 "#,
128 details.name
129 )?;
130 for variant in details.variants.iter() {
131 let f = &mut indented(f);
132 writeln!(f, "if self.{} != nil {{", variant.go_name())?;
133 match &details.representation {
134 UnionRepresentation::AdjacentlyTagged { tag, content } => {
135 write!(
136 indented(f),
137 "{}",
138 AdjacentlyTaggedMarshaller {
139 tag,
140 content,
141 variant
142 }
143 )?;
144 }
145 UnionRepresentation::InternallyTagged { tag } => {
146 write!(
147 indented(f),
148 "{}",
149 InternallyTaggedMarshaller { tag, variant }
150 )?;
151 }
152 _ => todo!("Implement the other tagging enum representations"),
153 }
154 write!(f, "}}")?;
155 write!(f, " else ")?;
156 }
157 writeln!(indented(f), "{{")?;
158 writeln!(
159 indented(f),
160 "\treturn nil, fmt.Errorf(\"No variant was present\")"
161 )?;
162 writeln!(indented(f), "}}")?;
163 writeln!(f, "}}")?;
164
165 Ok(())
166 }
167}
168
169impl UnionVariant {
170 fn go_name(&self) -> String {
171 match (&self.name, &self.ty) {
172 (Some(name), _) => name.clone(),
173 (_, FieldType::Named(type_ref)) => type_ref.name().to_string(),
174 _ => todo!("Variant must be named or named type for now (fix this later)"),
175 }
176 }
177}
178
179struct AdjacentlyTaggedMarshaller<'a> {
180 tag: &'a str,
181 content: &'a str,
182 variant: &'a UnionVariant,
183}
184
185impl fmt::Display for AdjacentlyTaggedMarshaller<'_> {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 writedoc! {
188 f,
189 r#"
190 output := make(map[string]interface{{}})
191 output["{tag}"] = "{serialized_name}"
192 output["{content}"] = self.{variant_go_name}
193 return json.Marshal(output)
194 "#,
195 tag = self.tag,
196 serialized_name = self.variant.serialized_name,
197 content = self.content,
198 variant_go_name = self.variant.go_name()
199 }
200 }
201}
202
203struct InternallyTaggedMarshaller<'a> {
204 tag: &'a str,
205 variant: &'a UnionVariant,
206}
207
208impl fmt::Display for InternallyTaggedMarshaller<'_> {
209 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210 writedoc! {
211 f,
212 r#"
213 return json.Marshal(struct{{
214 Tag string `json:"{tag}"`
215 {variant_type}
216 }}{{
217 Tag: "{serialized_name}",
218 {variant_type}: *self.{variant_go_name},
219 }})
220 "#,
221 tag = self.tag,
222 serialized_name = self.variant.serialized_name,
223 variant_go_name = self.variant.go_name(),
224 variant_type = self.variant.ty.go_type()
225 }
226 }
227}
228
229impl fmt::Display for UnionUnmarshal<'_> {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 let details = self.0;
236 writeln!(
237 f,
238 "func (self *{}) UnmarshalJSON(data []byte) error {{",
239 details.name
240 )?;
241 match &details.representation {
242 UnionRepresentation::AdjacentlyTagged { tag, content } => {
243 let f = &mut indented(f);
244 writeln!(
245 f,
246 "temp := struct {{\n\tTag string `json:\"{}\"`\n}}{{}}",
247 tag
248 )?;
249 writeln!(f, "if err := json.Unmarshal(data, &temp); err != nil {{")?;
250 writeln!(f, "\treturn err")?;
251 writeln!(f, "}}")?;
252 for variant in &details.variants {
253 write!(
254 f,
255 "{}",
256 AdjacentlyTaggedVariantUnmarshaller {
257 content,
258 variant,
259 all_variants: &details.variants
260 }
261 )?;
262 }
263 writeln!(f, "{{")?;
264 writeln!(indented(f), "return errors.New(\"Unknown type tag\")")?;
265 writeln!(f, "}}")?;
266 writeln!(f, "return nil")?;
267 }
268 UnionRepresentation::InternallyTagged { tag } => {
269 let f = &mut indented(f);
270 writeln!(
271 f,
272 "temp := struct{{\n\tTag string `json:\"{}\"`\n}}{{}}",
273 tag
274 )?;
275 writeln!(f, "if err := json.Unmarshal(data, &temp); err != nil {{")?;
276 writeln!(f, "\treturn err")?;
277 writeln!(f, "}}")?;
278 for variant in &details.variants {
279 write!(
280 f,
281 "{}",
282 InternallyTaggedVariantUnmarshaller {
283 variant,
284 all_variants: &details.variants
285 }
286 )?;
287 }
288 writeln!(f, "{{")?;
289 writeln!(indented(f), "return errors.New(\"Unknown type tag\")")?;
290 writeln!(f, "}}")?;
291 writeln!(f, "return nil")?;
292 }
293
294 _ => todo!("Support other enum representaitons"),
295 }
296 writeln!(f, "}}")?;
297
298 Ok(())
301 }
302}
303
304struct AdjacentlyTaggedVariantUnmarshaller<'a> {
305 content: &'a str,
306 variant: &'a UnionVariant,
307 all_variants: &'a [UnionVariant],
308}
309
310impl fmt::Display for AdjacentlyTaggedVariantUnmarshaller<'_> {
311 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312 writeln!(f, "if temp.Tag == \"{}\" {{", self.variant.serialized_name)?;
313 writeln!(
314 indented(f),
315 "rv := struct {{\n\tData {} `json:\"{}\"`\n}}{{}}",
316 self.variant.ty.go_type(),
317 self.content,
318 )?;
319 writeln!(
320 indented(f),
321 "if err := json.Unmarshal(data, &rv); err != nil {{"
322 )?;
323 writeln!(indented(f), "\treturn err")?;
324 writeln!(indented(f), "}}")?;
325 writeln!(indented(f), "self.{} = &rv.Data", self.variant.go_name())?;
326 for other_variant in self.all_variants {
327 if other_variant == self.variant {
328 continue;
329 }
330 writeln!(indented(f), "self.{} = nil", other_variant.go_name())?;
331 }
332 write!(f, "}} else ")
333 }
334}
335
336struct InternallyTaggedVariantUnmarshaller<'a> {
337 variant: &'a UnionVariant,
338 all_variants: &'a [UnionVariant],
339}
340
341impl fmt::Display for InternallyTaggedVariantUnmarshaller<'_> {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343 writedoc!(
344 f,
345 r#"
346 if temp.Tag == "{serialized_name}" {{
347 var rv {go_type}
348 if err := json.Unmarshal(data, &rv); err != nil {{
349 return err
350 }}
351 self.{go_name} = &rv
352 "#,
353 serialized_name = self.variant.serialized_name,
354 go_type = self.variant.ty.go_type(),
355 go_name = self.variant.go_name()
356 )?;
357 for other_variant in self.all_variants {
358 if other_variant == self.variant {
359 continue;
360 }
361 writeln!(indented(f), "self.{} = nil", other_variant.go_name())?;
362 }
363 write!(f, "}} else ")
364 }
365}
366
367fn to_pascal_case(s: &str) -> String {
368 let mut buf = String::with_capacity(s.len());
369 let mut prev_is_underscore = true;
370 for c in s.chars() {
371 if c == '_' {
372 prev_is_underscore = true;
373 } else if prev_is_underscore {
374 buf.push(c.to_ascii_uppercase());
375 prev_is_underscore = false;
376 } else {
377 buf.push(c.to_ascii_lowercase());
378 }
379 }
380 buf
381}
382
383#[cfg(test)]
384mod tests {
385 use insta::assert_snapshot;
386
387 use super::*;
388 use crate::types::TypeRef;
389
390 #[test]
391 fn test_primitive_structs() {
392 assert_snapshot!(
393 GoType::Struct(&Struct {
394 name: "MyStruct".into(),
395 fields: vec![
396 Field {
397 name: "a_string".into(),
398 serialized_name: "a_string".into(),
399 ty: FieldType::Primitive(Primitive::String),
400 },
401 Field {
402 name: "an_int".into(),
403 serialized_name: "renamed_tho".into(),
404 ty: FieldType::Primitive(Primitive::Int),
405 },
406 Field {
407 name: "a_bool".into(),
408 serialized_name: "also_renamed".into(),
409 ty: FieldType::Primitive(Primitive::Bool),
410 },
411 Field {
412 name: "a_float".into(),
413 serialized_name: "a_float".into(),
414 ty: FieldType::Primitive(Primitive::Float),
415 },
416 ],
417 })
418 .to_string(),
419 @r###"
420 type MyStruct struct {
421 AString string `json:"a_string"`
422 AnInt int `json:"renamed_tho"`
423 ABool bool `json:"also_renamed"`
424 AFloat float64 `json:"a_float"`
425 }
426 "###
427 );
428 }
429
430 #[test]
431 fn test_newtype_output() {
432 assert_snapshot!(GoType::NewType(&NewType {
433 name: "UserId".into(),
434 inner: FieldType::Primitive(Primitive::String),
435 })
436 .to_string(), @"type UserId string
437");
438 }
439
440 #[test]
441 fn test_enum_output() {
442 assert_snapshot!(GoType::Enum(&Enum {
443 name: "FulfilmentType".into(),
444 variants: vec![
445 EnumVariant {
446 name: "Delivery".into(),
447 serialized_name: "DELIVERY".into(),
448 },
449 EnumVariant {
450 name: "Collection".into(),
451 serialized_name: "COLLECTION".into(),
452 },
453 ],
454 })
455 .to_string(), @r###"
456 type FulfilmentType string
457
458 const (
459 FulfilmentTypeDelivery FulfilmentType = "DELIVERY"
460 FulfilmentTypeCollection FulfilmentType = "COLLECTION"
461 )
462 "###);
463 }
464
465 #[test]
466 fn test_adjacently_tagged_union_output() {
467 assert_snapshot!(GoType::Union(&Union {
468 name: "MyUnion".into(),
469 representation: UnionRepresentation::AdjacentlyTagged {
470 tag: "type".into(),
471 content: "data".into(),
472 },
473 variants: vec![
474 UnionVariant {
475 name: Some("VarOne".into()),
476 ty: FieldType::Named(TypeRef {
477 name: "VarOne".into()
478 }),
479 serialized_name: "VAR_ONE".into(),
480 },
481 UnionVariant {
482 name: Some("VarTwo".into()),
483 ty: FieldType::Named(TypeRef {
484 name: "VarTwo".into()
485 }),
486 serialized_name: "VAR_TWO".into(),
487 }
488 ]
489 })
490 .to_string());
491 }
492
493 #[test]
494 fn test_list_types() {
495 assert_snapshot!(
496 FieldType::List(Box::new(FieldType::Primitive(Primitive::String))).go_type(),
497 @"[]string"
498 );
499 }
500
501 #[test]
502 fn test_map_types() {
503 assert_snapshot!(
504 FieldType::Map{
505 key: Box::new(FieldType::Primitive(Primitive::String)),
506 value: Box::new(FieldType::Primitive(Primitive::Int))
507 }.go_type(),
508 @"map[string]int"
509 );
510 }
511
512 #[test]
513 fn test_option_types() {
514 assert_snapshot!(
515 FieldType::Optional(Box::new(FieldType::Primitive(Primitive::String))).go_type(),
516 @"*string"
517 );
518 }
519}