,
114 | // Pref: AsRef<[u8]>,
115 | // {
116 | // let mut file = match File::create(path.clone()) {
117 | // Ok(file) => file,
118 | // Err(err) => {
119 | // eprintln!("Error creating file: {:?}", path);
120 | // return Err(err);
121 | // }
122 | // };
123 | // file.write_all(prefix.as_ref())?;
124 | // file.write_all(self.prefix().as_bytes())?;
125 | // file.write_all("\n".as_ref())?;
126 | // Ok(file)
127 | // }
128 |
129 | /// finalize is called after all the types have been
130 | /// emitted into the destination file
131 | ///
132 | /// Here additional cleanup or post-procesing can be done,
133 | /// for example linting or code-formatting
134 | fn finalize(&mut self, path: P) -> Result<(), std::io::Error>
135 | where
136 | P: AsRef;
137 |
138 | /// The prefix method generates text for a prefix
139 | /// which will be prefixed to the target file.
140 | ///
141 | /// This would, for example, be the place to provide imports
142 | /// required for the target language or framework.
143 | fn prefix(&mut self) -> String;
144 | fn emit(&mut self) -> String
145 | where
146 | Self: Sized,
147 | {
148 | T::emit_with::(self)
149 | }
150 |
151 | /// Emit a struct representation from a struct type
152 | fn emit_struct(&mut self) -> String
153 | where
154 | T: StructType;
155 |
156 | /// Emit an enum representation from an enum type
157 | fn emit_enum(&mut self) -> String
158 | where
159 | T: EnumReflectionType;
160 |
161 | /// Emit a type-alias representation from an alias type
162 | fn emit_alias(&mut self) -> String
163 | where
164 | T: AliasType;
165 | }
166 |
167 | pub trait RustType {
168 | fn emit_rust(&self) -> String;
169 | }
170 |
--------------------------------------------------------------------------------
/type_reflect/src/rust.rs:
--------------------------------------------------------------------------------
1 | pub use super::struct_type::*;
2 | pub use super::type_description::Type;
3 | pub use super::*;
4 | use std::ffi::OsStr;
5 | use std::process::Command;
6 |
7 | #[derive(Default)]
8 | pub struct Rust {}
9 |
10 | const DERIVES: &str = "#[derive(Debug, Clone, Serialize, Deserialize)]";
11 |
12 | impl TypeEmitter for Rust {
13 | fn prefix(&mut self) -> String {
14 | "use serde::{Deserialize, Serialize};\nuse serde_json;\n".to_string()
15 | }
16 |
17 | fn emit_struct(&mut self) -> String
18 | where
19 | T: StructType,
20 | {
21 | format!("\n{}\n{}\n", DERIVES, T::rust())
22 | }
23 |
24 | fn emit_enum(&mut self) -> String
25 | where
26 | T: EnumReflectionType,
27 | {
28 | format!("\n{}\n{}\n", DERIVES, T::rust())
29 | }
30 |
31 | fn emit_alias(&mut self) -> String
32 | where
33 | T: AliasType,
34 | {
35 | format!("\n{}\n{}\n", DERIVES, T::rust())
36 | }
37 |
38 | fn finalize(&mut self, path: P) -> Result<(), std::io::Error>
39 | where
40 | P: AsRef,
41 | {
42 | let output = Command::new("rustfmt").arg(path).output()?;
43 | if !output.status.success() {
44 | eprintln!("Failed to format file");
45 | eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr));
46 | }
47 | Ok(())
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/type_reflect/src/struct_type.rs:
--------------------------------------------------------------------------------
1 | use type_reflect_core::{type_description::TypeFieldsDefinition, Inflection};
2 |
3 | /// A type implementing `StructType` can
4 | /// be used to emit a struct representation
5 | pub trait StructType {
6 | fn name() -> &'static str;
7 | fn inflection() -> Inflection;
8 | fn fields() -> TypeFieldsDefinition;
9 | fn rust() -> String;
10 | }
11 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_format.rs:
--------------------------------------------------------------------------------
1 | use std::{ffi::OsStr, path::Path};
2 |
3 | use dprint_plugin_typescript::{
4 | configuration::{
5 | ConfigurationBuilder, NextControlFlowPosition, PreferHanging, QuoteStyle,
6 | SameOrNextLinePosition,
7 | },
8 | FormatTextOptions,
9 | };
10 |
11 | use crate::{AliasType, EnumReflectionType, StructType, TypeEmitter};
12 |
13 | pub struct TSFormat {
14 | pub tab_size: u8,
15 | pub line_width: u32,
16 | }
17 |
18 | impl Default for TSFormat {
19 | fn default() -> Self {
20 | Self {
21 | tab_size: 2,
22 | line_width: 80,
23 | }
24 | }
25 | }
26 |
27 | impl TypeEmitter for TSFormat {
28 | fn prefix(&mut self) -> String {
29 | "".to_string()
30 | }
31 |
32 | fn emit_struct(&mut self) -> String
33 | where
34 | T: StructType,
35 | {
36 | "".to_string()
37 | }
38 |
39 | fn emit_enum(&mut self) -> String
40 | where
41 | T: EnumReflectionType,
42 | {
43 | "".to_string()
44 | }
45 |
46 | fn emit_alias(&mut self) -> String
47 | where
48 | T: AliasType,
49 | {
50 | "".to_string()
51 | }
52 |
53 | fn finalize(&mut self, path: P) -> Result<(), std::io::Error>
54 | where
55 | P: AsRef,
56 | {
57 | // build the configuration once
58 | let config = ConfigurationBuilder::new()
59 | .indent_width(self.tab_size)
60 | .line_width(self.line_width)
61 | .build();
62 |
63 | let file_path = Path::new(&path);
64 |
65 | let text: String = std::fs::read_to_string(Path::new(&path))?;
66 |
67 | let options: FormatTextOptions = FormatTextOptions {
68 | path: Path::new(&path),
69 | extension: None,
70 | text,
71 | config: &config,
72 | external_formatter: None,
73 | };
74 |
75 | let result = dprint_plugin_typescript::format_text(options);
76 |
77 | match result {
78 | Ok(Some(contents)) => {
79 | std::fs::write(file_path, contents)?;
80 | }
81 | Err(err) => {
82 | eprintln!("Error formatting typescript: {}", err);
83 | }
84 | _ => {
85 | eprintln!("Failed to format text: no output generated");
86 | }
87 | };
88 |
89 | Ok(())
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/enum_type/case_type.rs:
--------------------------------------------------------------------------------
1 | use ts_quote::ts_string;
2 | use type_reflect_core::{EnumCase, Inflection, NamedField, Type};
3 |
4 | use crate::ts_validation::{
5 | struct_type::named_field_validations, validation::tuple_validation, validation_namespace,
6 | };
7 |
8 | pub fn emit_complex_enum_case_type(
9 | enum_name: &str,
10 | case_key: &String,
11 | content_key: &Option,
12 | case: EnumCase,
13 | ) -> String {
14 | let case_key_value: String = format!("{}CaseKey.{}", enum_name, case.name);
15 | let case_type_name: String = format!("{}Case{}", enum_name, case.name);
16 |
17 | let validator = match &case.type_ {
18 | type_reflect_core::TypeFieldsDefinition::Unit => emit_simple_case_type_validator(),
19 | type_reflect_core::TypeFieldsDefinition::Tuple(members) => {
20 | emit_tuple_case_type_validator(content_key, &members)
21 | }
22 | type_reflect_core::TypeFieldsDefinition::Named(members) => {
23 | emit_struct_case_type_validator(content_key, &members, case.inflection)
24 | }
25 | };
26 |
27 | let validation_impl = match &case.type_ {
28 | type_reflect_core::TypeFieldsDefinition::Unit => {
29 | ts_string! {
30 | if (!isRecord(input)) {
31 | throw new Error(#"`Error parsing #case_type_name: expected: Record, found: ${typeof input}`");
32 | }
33 | if (input.#case_key !== #case_key_value) {
34 | throw new Error(#"`Error parsing #case_type_name: expected key: #case_key_value, found: ${typeof input}`");
35 | }
36 | return input as #case_type_name
37 | }
38 | }
39 | _ => {
40 | ts_string! {
41 | if (!isRecord(input)) {
42 | throw new Error(#"`Error parsing #case_type_name: expected: Record, found: ${typeof input}`");
43 | }
44 | if (input.#case_key !== #case_key_value) {
45 | throw new Error(#"`Error parsing #case_type_name: expected key: #case_key_value, found: ${typeof input}`");
46 | }
47 | #validator
48 | return input as #case_type_name
49 | }
50 | }
51 | };
52 |
53 | return validation_namespace(case_type_name.as_str(), validation_impl.as_str());
54 | }
55 |
56 | fn emit_simple_case_type_validator() -> String {
57 | String::new()
58 | }
59 |
60 | fn emit_struct_case_type_validator(
61 | content_key: &Option,
62 | members: &Vec,
63 | inflection: Inflection,
64 | ) -> String {
65 | let member_prefix = match content_key {
66 | None => "input".to_string(),
67 | Some(key) => format!("input.{}", key),
68 | };
69 | named_field_validations(member_prefix.as_str(), members, inflection)
70 | }
71 |
72 | fn emit_tuple_case_type_validator(content_key: &Option, members: &Vec) -> String {
73 | let member_prefix = match content_key {
74 | None => "input".to_string(),
75 | Some(key) => format!("input.{}", key),
76 | };
77 | tuple_validation(member_prefix.as_str(), members)
78 | }
79 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/enum_type/complex.rs:
--------------------------------------------------------------------------------
1 | use type_reflect_core::EnumCase;
2 |
3 | use crate::{ts_validation::validation_namespace, EnumReflectionType};
4 |
5 | use super::case_type::emit_complex_enum_case_type;
6 | use ts_quote::ts_string;
7 |
8 | pub fn emit_complex_enum_type(case_key: &String, content_key: &Option) -> String
9 | where
10 | T: EnumReflectionType,
11 | {
12 | let case_type_validators: String = T::cases()
13 | .into_iter()
14 | .map(|case: EnumCase| emit_complex_enum_case_type(T::name(), case_key, content_key, case))
15 | .collect();
16 |
17 | let case_validations: String = T::cases()
18 | .into_iter()
19 | .map(|case: EnumCase| validate_case(T::name(), &case))
20 | .collect();
21 |
22 | let name = T::name();
23 |
24 | let namespace = validation_namespace(T::name(), ts_string! {
25 | #case_validations
26 | throw new Error(#"`Error validating #name: value ${JSON.stringify(input)} does not match any variant`");
27 | }.as_str());
28 |
29 | ts_string! {
30 | #case_type_validators
31 | #namespace
32 | }
33 | }
34 |
35 | fn validate_case(type_name: &str, case: &EnumCase) -> String {
36 | let case_type = format!("{}Case{}", type_name, case.name);
37 |
38 | ts_string! {
39 | try {
40 | return #case_type.validate(input);
41 | } catch {}
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/enum_type/mod.rs:
--------------------------------------------------------------------------------
1 | use type_reflect_core::EnumType;
2 | use untagged::emit_untagged_enum_type;
3 |
4 | use crate::{ts_validation::validation_namespace, EnumReflectionType};
5 |
6 | mod complex;
7 | use complex::*;
8 |
9 | mod case_type;
10 |
11 | mod untagged;
12 |
13 | pub fn emit_enum_type() -> String
14 | where
15 | T: EnumReflectionType,
16 | {
17 | match T::enum_type() {
18 | EnumType::Simple => emit_simple_enum_type::(),
19 | EnumType::Complex {
20 | case_key,
21 | content_key,
22 | } => emit_complex_enum_type::(&case_key, &content_key),
23 | EnumType::Untagged => emit_untagged_enum_type::(),
24 | }
25 | }
26 |
27 | fn emit_simple_enum_type() -> String
28 | where
29 | T: EnumReflectionType,
30 | {
31 | let validation_impl = format!(
32 | r#"
33 | if(Object.values({name}).includes(input as {name})) {{
34 | return input as {name};
35 | }}
36 | throw new Error(`Error parsing {name}: value does not conform: ${{JSON.stringify(input)}}`)
37 | "#,
38 | name = T::name(),
39 | );
40 | validation_namespace(T::name(), validation_impl.as_str())
41 | }
42 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/enum_type/untagged/case_type.rs:
--------------------------------------------------------------------------------
1 | use ts_quote::ts_string;
2 | use type_reflect_core::{EnumCase, Type};
3 |
4 | use crate::ts_validation::{
5 | struct_type::named_field_validations, validation::tuple_validation, validation_namespace,
6 | };
7 |
8 | pub fn emit_case_type(case: &EnumCase, parent_name: &str) -> String {
9 | let case_type = format!("{}Case{}", parent_name, case.name);
10 | let validation_impl = match &case.type_ {
11 | type_reflect_core::TypeFieldsDefinition::Unit => {
12 | unreachable!("Unit cases don't emit case types");
13 | }
14 | type_reflect_core::TypeFieldsDefinition::Tuple(members) => {
15 | if members.len() == 1 {
16 | return "".to_string();
17 | }
18 | tuple_validation("input", &members)
19 | }
20 | type_reflect_core::TypeFieldsDefinition::Named(fields) => {
21 | let val = named_field_validations("input", &fields, case.inflection);
22 | ts_string! {
23 | if (!isRecord(input)) {
24 | throw new Error(#r#"`Error parsing #case_type: expected: Record, found: ${typeof input}`"#);
25 | }
26 | #val
27 | }
28 | }
29 | };
30 |
31 | let validation_impl = ts_string! {
32 | #validation_impl
33 | return input as #case_type;
34 | };
35 |
36 | validation_namespace(&case_type, &validation_impl)
37 | }
38 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/enum_type/untagged/mod.rs:
--------------------------------------------------------------------------------
1 | use case_type::emit_case_type;
2 | use ts_quote::ts_string;
3 | use type_reflect_core::{EnumCase, Inflectable, TypeFieldsDefinition};
4 | use union_case::union_case_validation;
5 | use unit_case::unit_case_validation;
6 |
7 | use crate::{ts_validation::validation_namespace, EnumReflectionType};
8 | mod case_type;
9 | mod union_case;
10 | mod unit_case;
11 |
12 | pub fn emit_untagged_enum_type() -> String
13 | where
14 | T: EnumReflectionType,
15 | {
16 | let name = T::name();
17 | let cases = T::cases();
18 | let inflection = T::inflection();
19 |
20 | let unit_cases: Vec = cases
21 | .iter()
22 | .filter(|c| {
23 | if let TypeFieldsDefinition::Unit = c.type_ {
24 | true
25 | } else {
26 | false
27 | }
28 | })
29 | .map(|case| {
30 | let name = case.name.inflect(inflection);
31 | name
32 | })
33 | .collect();
34 |
35 | let unit_case_validations = if unit_cases.is_empty() {
36 | "".to_string()
37 | } else {
38 | let unit_case_validations: Vec<_> = unit_cases
39 | .into_iter()
40 | .map(|case_name| unit_case_validation(case_name.as_str(), &name))
41 | .collect();
42 | let unit_case_validations = unit_case_validations.join("\n");
43 | ts_string! {
44 | if (#"'string'" === typeof input) {
45 | #unit_case_validations
46 | throw new Error(#"`Error validating #name: none of the unit cases were matched`");
47 | }
48 | }
49 | };
50 |
51 | let union_cases: Vec<&EnumCase> = cases
52 | .iter()
53 | .filter(|c| {
54 | if let TypeFieldsDefinition::Unit = c.type_ {
55 | false
56 | } else {
57 | true
58 | }
59 | })
60 | .collect();
61 |
62 | let union_case_validations = if union_cases.is_empty() {
63 | "".to_string()
64 | } else {
65 | let union_case_validations: Vec<_> = union_cases
66 | .iter()
67 | .map(|case| union_case_validation(case, &name, inflection))
68 | .collect();
69 | let union_case_validations = union_case_validations.join("\n");
70 | ts_string! {
71 | if (!isRecord(input)) {
72 | throw new Error(#r#"`Error parsing #name: expected: Record, found: ${typeof input}`"#);
73 | }
74 | #union_case_validations
75 | }
76 | };
77 |
78 | let union_case_types: Vec<_> = union_cases
79 | .iter()
80 | .map(|case| emit_case_type(case, &name))
81 | .collect();
82 |
83 | let union_case_types = union_case_types.join("\n");
84 |
85 | let namespace = validation_namespace(
86 | &name,
87 | &ts_string! {
88 | #unit_case_validations
89 | #union_case_validations
90 | throw new Error(#"`Error validating #name: none of the union cases were matched`");
91 | },
92 | );
93 |
94 | ts_string! {
95 |
96 | #union_case_types
97 | #namespace
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/enum_type/untagged/union_case.rs:
--------------------------------------------------------------------------------
1 | use ts_quote::ts_string;
2 | use type_reflect_core::{inflection, EnumCase, Inflectable, Inflection, Type};
3 |
4 | use crate::{
5 | ts_validation::validation::type_validation,
6 | type_script::untagged_enum_type::emit_case_type_name,
7 | };
8 |
9 | pub fn union_case_validation(case: &EnumCase, parent_name: &str, inflection: Inflection) -> String {
10 | let case_key = case.name.inflect(inflection);
11 | let case_type_name = emit_case_type_name(case, parent_name);
12 |
13 | let case_validation = match &case.type_ {
14 | type_reflect_core::TypeFieldsDefinition::Unit => {
15 | unreachable!("Unit cases are handled separately");
16 | }
17 | type_reflect_core::TypeFieldsDefinition::Tuple(items) => {
18 | validate_tuple_case(case, &items, parent_name, &case_key)
19 | }
20 | type_reflect_core::TypeFieldsDefinition::Named(_) => {
21 | validate_struct_case(case, parent_name, &case_key)
22 | }
23 | };
24 |
25 | ts_string! {
26 | if (input.#case_key) {
27 | #case_validation
28 | }
29 | }
30 | }
31 |
32 | fn validate_tuple_case(
33 | case: &EnumCase,
34 | tuple_members: &Vec,
35 | parent_name: &str,
36 | case_key: &str,
37 | ) -> String {
38 | if tuple_members.len() == 1 {
39 | let Some(case_type) = tuple_members.first() else {
40 | return "_ERROR_NO_CASE_TYPE_EXISTS_".to_string();
41 | };
42 | let var_name = ts_string! { input.#case_key };
43 | let val = type_validation(&var_name, case_type);
44 | ts_string! {
45 | #val
46 | return input as #parent_name;
47 | }
48 | } else {
49 | let case_type = format!("{}Case{}", parent_name, case.name);
50 | ts_string! {
51 | return { #case_key: #case_type.validate(input.#case_key) };
52 | }
53 | }
54 | }
55 |
56 | fn validate_struct_case(case: &EnumCase, parent_name: &str, case_key: &str) -> String {
57 | let case_type = format!("{}Case{}", parent_name, case.name);
58 | ts_string! {
59 | return { #case_key: #case_type.validate(input.#case_key) };
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/enum_type/untagged/unit_case.rs:
--------------------------------------------------------------------------------
1 | use ts_quote::ts_string;
2 |
3 | pub fn unit_case_validation(case_name: &str, type_name: &str) -> String {
4 | ts_string! {
5 | if (input === #"'#case_name'") {
6 | return input as #type_name
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/mod.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::OsStr;
2 |
3 | use crate::{AliasType, EnumReflectionType, StructType, TypeEmitter};
4 |
5 | mod struct_type;
6 | use struct_type::struct_impl;
7 |
8 | mod enum_type;
9 | use enum_type::emit_enum_type;
10 | use ts_quote::ts_string;
11 |
12 | mod validation;
13 |
14 | #[derive(Default)]
15 | pub struct TSValidation {}
16 |
17 | impl TypeEmitter for TSValidation {
18 | fn prefix(&mut self) -> String {
19 | r#"
20 | function isRecord(value: any): value is Record {
21 | return typeof value === 'object' && value !== null && !Array.isArray(value);
22 | }
23 | "#
24 | .to_string()
25 | }
26 |
27 | fn emit_struct(&mut self) -> String
28 | where
29 | T: StructType,
30 | {
31 | let name = T::name();
32 | struct_impl(&name, &T::fields(), T::inflection())
33 | }
34 |
35 | fn emit_enum(&mut self) -> String
36 | where
37 | T: EnumReflectionType,
38 | {
39 | emit_enum_type::()
40 | }
41 |
42 | fn emit_alias(&mut self) -> String
43 | where
44 | T: AliasType,
45 | {
46 | "".to_string()
47 | }
48 |
49 | fn finalize(&mut self, _path: P) -> Result<(), std::io::Error>
50 | where
51 | P: AsRef,
52 | {
53 | Ok(())
54 | }
55 | }
56 |
57 | pub fn validation_namespace(name: &str, validation_impl: &str) -> String {
58 | ts_string! {
59 | export namespace #name {
60 | export function validate(input: any): #name {
61 | #validation_impl
62 | }
63 |
64 | export function parse(input: string): #name {
65 | let json = JSON.parse(input);
66 | return validate(json);
67 | }
68 |
69 | export function tryValidate(input: any): #name | undefined {
70 | try {
71 | return validate(input);
72 | } catch {
73 | return undefined;
74 | }
75 | }
76 |
77 | export function tryParse(input: string): #name | undefined {
78 | let json = JSON.parse(input);
79 | return tryValidate(json);
80 | }
81 |
82 | export function validateArray(input: any): Array<#name> {
83 | if (!Array.isArray(input)) {
84 | throw new Error(#"`Error validating Array<#name>: expected: Array, found: ${ typeof input }`");
85 | }
86 | for (const item of input) {
87 | validate(item);
88 | }
89 | return input as Array<#name>;
90 | }
91 |
92 | export function parseArray(input: string): Array<#name> {
93 | let json = JSON.parse(input);
94 | return validateArray(json);
95 | }
96 |
97 | export function tryValidateArray(input: any): Array<#name> | undefined {
98 | try {
99 | return validateArray(input);
100 | } catch (e: any) {
101 | return undefined;
102 | }
103 | }
104 |
105 | export function tryParseArray(input: any): Array<#name> | undefined {
106 | try {
107 | return parseArray(input);
108 | } catch (e: any) {
109 | return undefined;
110 | }
111 | }
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/struct_type.rs:
--------------------------------------------------------------------------------
1 | use type_reflect_core::{Inflectable, Inflection, NamedField, TypeFieldsDefinition};
2 |
3 | use super::{
4 | validation::{tuple_validation, type_validation},
5 | validation_namespace,
6 | };
7 | use ts_quote::*;
8 |
9 | pub fn named_field_validations(
10 | member_prefix: &str,
11 | members: &Vec,
12 | inflection: Inflection,
13 | ) -> String {
14 | let members: Vec = members
15 | .into_iter()
16 | .map(|member| {
17 | let member_name = member.name.inflect(inflection);
18 | type_validation(
19 | ts_string! {
20 | #{member_prefix}.#{member_name}
21 | }
22 | .as_str(),
23 | &member.type_,
24 | )
25 |
26 | // type_validation(
27 | // format!("{}.{}", member_prefix, member_name).as_str(),
28 | // &member.type_,
29 | // )
30 | })
31 | .collect();
32 | members.join("\n ")
33 | }
34 |
35 | pub fn struct_field_validations(
36 | member_prefix: &str,
37 | fields: &TypeFieldsDefinition,
38 | inflection: Inflection,
39 | ) -> String {
40 | match fields {
41 | TypeFieldsDefinition::Unit => todo!(),
42 | TypeFieldsDefinition::Tuple(tuple) => tuple_validation(member_prefix, tuple),
43 | TypeFieldsDefinition::Named(named) => {
44 | named_field_validations(member_prefix, named, inflection)
45 | }
46 | }
47 | }
48 |
49 | pub fn struct_impl(name: &str, fields: &TypeFieldsDefinition, inflection: Inflection) -> String {
50 | let validations = struct_field_validations("input", fields, inflection);
51 |
52 | let validation_impl = match fields {
53 | TypeFieldsDefinition::Unit => todo!(),
54 | TypeFieldsDefinition::Tuple(_) => {
55 | ts_string! {
56 | #validations
57 | return input as #name;
58 | }
59 | }
60 | TypeFieldsDefinition::Named(_) => ts_string! {
61 | if (!isRecord(input)) {
62 | throw new Error(#r#"`Error parsing #name#: expected: Record, found: ${typeof input}`"#);
63 | }
64 | #validations
65 | return input as #name;
66 | },
67 | };
68 |
69 | // let validation_impl = format!(
70 | // r#"
71 | // if (!isRecord(input)) {{
72 | // throw new Error(`Error parsing {name}: expected: Record, found: ${{typeof input}}`);
73 | // }}
74 | // {validations}
75 | // return input as {name};
76 | // "#,
77 | // name = name,
78 | // validations = validations
79 | // );
80 |
81 | validation_namespace(name, validation_impl.as_str())
82 | }
83 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/validation/array.rs:
--------------------------------------------------------------------------------
1 | use ts_quote::ts_string;
2 | use type_reflect_core::Type;
3 |
4 | use crate::ts_validation::validation::type_validation;
5 |
6 | pub fn array_validation(var_name: &str, member_type: &Type) -> String {
7 | let validation = type_validation("item", member_type);
8 | ts_string! {
9 | if (!Array.isArray(#var_name)) {
10 | throw new Error(#"`Error parsing #var_name: expected: Array, found: ${ typeof #var_name }`");
11 | }
12 | for (const item of #var_name) {
13 | #validation
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/validation/map.rs:
--------------------------------------------------------------------------------
1 | use type_reflect_core::Type;
2 |
3 | use crate::ts_validation::validation::type_validation;
4 |
5 | pub fn map_validation(var_name: &str, member_type: &Type) -> String {
6 | let validation = type_validation("item", member_type);
7 | format!(
8 | r#"
9 | if (!isRecord({var_name})) {{
10 | throw new Error(`Error parsing {var_name}: expected: Record, found: ${{ typeof {var_name} }}`);
11 | }}
12 | for (const key in {var_name}) {{
13 | const item = {var_name}[key];
14 | {validation}
15 | }}
16 | "#,
17 | var_name = var_name,
18 | validation = validation,
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/validation/mod.rs:
--------------------------------------------------------------------------------
1 | mod type_validation;
2 | pub use type_validation::*;
3 |
4 | mod primitive;
5 | pub use primitive::*;
6 |
7 | mod array;
8 | pub use array::*;
9 |
10 | mod map;
11 |
12 | mod tuple;
13 | pub use tuple::*;
14 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/validation/primitive.rs:
--------------------------------------------------------------------------------
1 | pub fn primitive_type_validation(var_name: &str, primitive_type: &str) -> String {
2 | format!(
3 | r#"
4 | if ('{primitive_type}' !== typeof {var_name}) {{
5 | throw new Error(`Validation error: expected: {primitive_type}, found: ${{ typeof {var_name} }}`);
6 | }}
7 | "#,
8 | var_name = var_name,
9 | primitive_type = primitive_type
10 | )
11 | }
12 |
13 | // Better error?
14 | // throw new Error(`Error parsing {parent_name}.{name}: expected: {primitive}, found: ${{ typeof input.{name} }}`);
15 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/validation/tuple.rs:
--------------------------------------------------------------------------------
1 | use crate::ts_validation::validation::type_validation;
2 | use ts_quote::ts_string;
3 | use type_reflect_core::Type;
4 |
5 | pub fn tuple_validation(var_name: &str, member_types: &Vec) -> String {
6 | if member_types.len() == 1 {
7 | return type_validation(var_name, &member_types[0]);
8 | }
9 |
10 | let member_validations: String = member_types
11 | .into_iter()
12 | .enumerate()
13 | .map(|(i, member)| {
14 | type_validation(
15 | ts_string! {
16 | #var_name[#i]
17 | }
18 | .as_str(),
19 | &member,
20 | )
21 | })
22 | .collect();
23 |
24 | ts_string! {
25 | if (!Array.isArray(#var_name)) {
26 | throw new Error(#"`Error parsing #var_name: expected: Array, found: ${ typeof #var_name }`");
27 | }
28 | #member_validations
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/type_reflect/src/ts_validation/validation/type_validation.rs:
--------------------------------------------------------------------------------
1 | use type_reflect_core::Type;
2 |
3 | use crate::type_script::to_ts_type;
4 |
5 | use super::{array_validation, map::map_validation, primitive_type_validation};
6 |
7 | pub fn type_validation(var_name: &str, type_: &Type) -> String {
8 | match type_ {
9 | Type::String => primitive_type_validation(var_name, "string"),
10 | Type::Float | Type::Int | Type::UnsignedInt => {
11 | primitive_type_validation(var_name, "number")
12 | }
13 | Type::Boolean => primitive_type_validation(var_name, "boolean"),
14 | Type::Array(t) => array_validation(var_name, &t),
15 | Type::Map { key: _, value } => map_validation(var_name, value),
16 | Type::Option(t) => {
17 | let type_validation = type_validation(var_name, &t);
18 | format!(
19 | r#"
20 | if ({var_name}) {{
21 | {type_validation}
22 | }}
23 | "#,
24 | var_name = var_name,
25 | type_validation = type_validation
26 | )
27 | }
28 | Type::Named(_) => {
29 | let value_type = to_ts_type(type_);
30 | format!(
31 | r#"
32 | {value_type}.validate({var_name})
33 | "#,
34 | var_name = var_name,
35 | value_type = value_type
36 | )
37 | }
38 | Type::Transparent(type_) => type_validation(var_name, &*(type_.type_)),
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/type_reflect/src/type_script/alias_type.rs:
--------------------------------------------------------------------------------
1 | use crate::AliasType;
2 |
3 | use super::to_ts_type;
4 |
5 | pub fn emit_alias_type() -> String
6 | where
7 | T: AliasType,
8 | {
9 | return format!(
10 | r#"
11 |
12 | export type {alias} = {source};
13 |
14 | "#,
15 | alias = T::name(),
16 | source = to_ts_type(&T::source_type())
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/type_reflect/src/type_script/enum_type.rs:
--------------------------------------------------------------------------------
1 | use ts_quote::ts_string;
2 | use type_reflect_core::{EnumCase, EnumType, Inflectable, Inflection};
3 |
4 | use super::untagged_enum_type::emit_untaggedd_enum_type;
5 | use crate::type_script::type_fields;
6 | use crate::EnumReflectionType;
7 |
8 | use super::to_ts_type;
9 |
10 | pub fn emit_enum_type() -> String
11 | where
12 | T: EnumReflectionType,
13 | {
14 | match T::enum_type() {
15 | EnumType::Simple => emit_simple_enum_type::(),
16 | EnumType::Complex {
17 | case_key,
18 | content_key,
19 | } => emit_complex_enum_type::(&case_key, &content_key),
20 | EnumType::Untagged => emit_untaggedd_enum_type::(),
21 | }
22 | }
23 |
24 | fn emit_simple_enum_type() -> String
25 | where
26 | T: EnumReflectionType,
27 | {
28 | let inflection = T::inflection();
29 | let simple_cases: String = T::cases()
30 | .into_iter()
31 | .map(|case| {
32 | let inflected = case.name.inflect(inflection);
33 | format!(
34 | r#" {name} = "{inflected}",
35 | "#,
36 | name = case.name
37 | )
38 | })
39 | .collect();
40 |
41 | format!(
42 | r#"
43 | export enum {name} {{
44 | {simple_cases}}}
45 | "#,
46 | name = T::name(),
47 | simple_cases = simple_cases,
48 | )
49 | }
50 |
51 | fn emit_complex_enum_type(case_key: &String, content_key: &Option) -> String
52 | where
53 | T: EnumReflectionType,
54 | {
55 | let cases_union = T::generate_cases_union();
56 | let case_keys_const = T::generate_case_key_const();
57 | let union_types = T::generate_union_types(&case_key, &content_key, T::inflection());
58 | let union_type = T::generate_union_schema();
59 |
60 | // Generate case type
61 |
62 | format!(
63 | r#"
64 | {cases_union}
65 |
66 | {case_keys_const}
67 | {union_types}
68 | {union_type}
69 | "#
70 | )
71 | }
72 |
73 | trait EnumTypeBridge: EnumReflectionType {
74 | fn case_type_name() -> String {
75 | format!("{}Case", Self::name())
76 | }
77 |
78 | fn case_key_const_name() -> String {
79 | format!("{}CaseKey", Self::name())
80 | }
81 |
82 | fn generate_cases_union() -> String {
83 | let mut case_values = vec![];
84 | let inflection = Self::inflection();
85 |
86 | for case in Self::cases() {
87 | let inflected = case.name.inflect(inflection);
88 | case_values.push(format!(r#""{inflected}""#));
89 | }
90 |
91 | let case_values = case_values.join("\n | ");
92 |
93 | let name = Self::case_type_name();
94 | let cases = case_values;
95 |
96 | ts_string! {
97 | export type #name = #cases;
98 | }
99 | }
100 |
101 | fn generate_case_key_const() -> String {
102 | let mut case_values = String::new();
103 | let inflection = Self::inflection();
104 |
105 | case_values.push_str("\n ");
106 | for case in Self::cases() {
107 | let inflected = case.name.inflect(inflection);
108 | case_values.push_str(&format!(r#"{name}: "{inflected}""#, name = case.name,));
109 | case_values.push_str(",\n ");
110 | }
111 |
112 | let name = Self::case_key_const_name();
113 | let cases = case_values;
114 |
115 | ts_string! {
116 | export const #name = {
117 | #cases
118 | };
119 | }
120 | }
121 |
122 | fn generate_union_types(
123 | case_key: &String,
124 | content_key: &Option,
125 | inflection: Inflection,
126 | ) -> String {
127 | let mut result = String::new();
128 |
129 | for case in Self::cases() {
130 | result.push_str(
131 | Self::generate_union_type(&case, &case_key, &content_key, inflection).as_str(),
132 | )
133 | }
134 |
135 | result
136 | }
137 |
138 | fn generate_union_type(
139 | case: &EnumCase,
140 | case_key: &String,
141 | content_key: &Option,
142 | inflection: Inflection,
143 | ) -> String {
144 | let case_type_name = union_case_type_name(case, Self::name());
145 | // let id = Self::case_id(case);
146 | let id = &case.name.inflect(inflection);
147 |
148 | let additional_fields = match &case.type_ {
149 | type_reflect_core::TypeFieldsDefinition::Unit => {
150 | return format!(
151 | r#"
152 | export type {case_type_name} = {{
153 | {case_key}: "{id}",
154 | }};
155 | "#
156 | )
157 | }
158 | type_reflect_core::TypeFieldsDefinition::Tuple(inner) => {
159 | let content_key = match content_key {
160 | Some(content_key) => content_key,
161 | None => {
162 | //TODO: make this a localized Syn error
163 | panic!("Content key required on enums containing at least one tuple-type variant.")
164 | }
165 | };
166 | if inner.len() == 1 {
167 | let type_ = to_ts_type(&inner[0]);
168 | format!(
169 | r#"{content_key}: {type_}"#,
170 | type_ = type_,
171 | content_key = content_key,
172 | )
173 | } else {
174 | let tuple_items: Vec =
175 | inner.into_iter().map(|item| to_ts_type(&item)).collect();
176 | let tuple_items: String = tuple_items.join(",\n ");
177 |
178 | format!(
179 | r#"{content_key}: [
180 | {tuple_items}
181 | ]"#,
182 | tuple_items = tuple_items,
183 | content_key = content_key,
184 | )
185 | }
186 | }
187 | type_reflect_core::TypeFieldsDefinition::Named(inner) => {
188 | let struct_items = type_fields::named_fields(inner, case.inflection);
189 |
190 | match content_key {
191 | Some(content_key) => format!(
192 | r#"{content_key}: {{
193 | {struct_items}
194 | }}"#,
195 | struct_items = struct_items,
196 | content_key = content_key,
197 | ),
198 | None => struct_items,
199 | }
200 | }
201 | };
202 | format!(
203 | r#"
204 | export type {case_type_name} = {{
205 | {case_key}: "{id}",
206 | {additional_fields}
207 | }};
208 | "#
209 | )
210 | }
211 |
212 | fn generate_union_schema() -> String {
213 | let cases: Vec = Self::cases()
214 | .into_iter()
215 | .map(|case| union_case_type_name(&case, Self::name()))
216 | .collect();
217 |
218 | let cases = cases.join("\n | ");
219 |
220 | format!(
221 | r#"
222 | export type {name} = {cases};
223 | "#,
224 | cases = cases,
225 | name = Self::name()
226 | )
227 | }
228 | }
229 |
230 | pub fn union_case_type_name(case: &EnumCase, parent_name: &str) -> String {
231 | format!("{}Case{}", parent_name, case.name)
232 | }
233 |
234 | impl EnumTypeBridge for T where T: EnumReflectionType {}
235 |
--------------------------------------------------------------------------------
/type_reflect/src/type_script/mod.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::OsStr;
2 |
3 | pub use super::struct_type::*;
4 | pub use super::type_description::Type;
5 | use super::*;
6 |
7 | pub mod struct_type;
8 | use dprint_plugin_typescript::{
9 | configuration::{
10 | ConfigurationBuilder, NextControlFlowPosition, PreferHanging, QuoteStyle,
11 | SameOrNextLinePosition,
12 | },
13 | FormatTextOptions,
14 | };
15 | use struct_type::*;
16 |
17 | pub mod enum_type;
18 | pub use enum_type::*;
19 | pub mod untagged_enum_type;
20 |
21 | pub mod type_fields;
22 | pub use type_fields::*;
23 |
24 | mod alias_type;
25 | pub use alias_type::*;
26 |
27 | pub struct TypeScript {
28 | pub tab_size: u32,
29 | }
30 |
31 | impl Default for TypeScript {
32 | fn default() -> Self {
33 | Self { tab_size: 2 }
34 | }
35 | }
36 |
37 | pub trait TypeExporter {
38 | fn export() -> String;
39 | }
40 |
41 | pub fn to_ts_type(t: &Type) -> String {
42 | match t {
43 | // TODO: Support generics
44 | Type::Named(t) => format!("{}", t.name),
45 | Type::String => "string".to_string(),
46 | Type::Int => "number".to_string(),
47 | Type::UnsignedInt => "number".to_string(),
48 | Type::Float => "number".to_string(),
49 | Type::Boolean => "boolean".to_string(),
50 | Type::Option(t) => format!("{}", to_ts_type(t)),
51 | Type::Array(t) => format!("Array<{}>", to_ts_type(t)),
52 | Type::Map { key, value } => {
53 | format!(
54 | "{{[key: {k}]: {v}}}",
55 | k = to_ts_type(key),
56 | v = to_ts_type(value)
57 | )
58 | }
59 | Type::Transparent(t) => to_ts_type(&*(t.type_)),
60 | }
61 | }
62 |
63 | impl TypeEmitter for TypeScript {
64 | fn prefix(&mut self) -> String {
65 | "".to_string()
66 | }
67 |
68 | fn emit_struct(&mut self) -> String
69 | where
70 | T: StructType,
71 | {
72 | let name = T::name();
73 | struct_impl(&name, &T::fields(), T::inflection())
74 | }
75 |
76 | fn emit_enum(&mut self) -> String
77 | where
78 | T: EnumReflectionType,
79 | {
80 | emit_enum_type::()
81 | }
82 |
83 | fn emit_alias(&mut self) -> String
84 | where
85 | T: AliasType,
86 | {
87 | emit_alias_type::()
88 | }
89 |
90 | fn finalize(&mut self, path: P) -> Result<(), std::io::Error>
91 | where
92 | P: AsRef,
93 | {
94 | // build the configuration once
95 | let config = ConfigurationBuilder::new()
96 | .indent_width(self.tab_size as u8)
97 | .line_width(80)
98 | .build();
99 |
100 | let file_path = Path::new(&path);
101 |
102 | let text: String = std::fs::read_to_string(Path::new(&path))?;
103 |
104 | let options: FormatTextOptions = FormatTextOptions {
105 | path: Path::new(&path),
106 | extension: None,
107 | text,
108 | config: &config,
109 | external_formatter: None,
110 | };
111 |
112 | let result = dprint_plugin_typescript::format_text(options);
113 |
114 | match result {
115 | Ok(Some(contents)) => {
116 | std::fs::write(file_path, contents)?;
117 | }
118 | Err(e) => {
119 | eprintln!("Error formatting typescript: {}", e);
120 | }
121 | _ => {}
122 | };
123 |
124 | Ok(())
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/type_reflect/src/type_script/struct_type.rs:
--------------------------------------------------------------------------------
1 | use super::{named_fields, tuple_fields};
2 | use ts_quote::ts_string;
3 | use type_reflect_core::{Inflection, TypeFieldsDefinition};
4 |
5 | pub fn struct_impl(name: &str, fields: &TypeFieldsDefinition, inflection: Inflection) -> String {
6 | let fields = match fields {
7 | TypeFieldsDefinition::Unit => todo!(),
8 | TypeFieldsDefinition::Tuple(tuple) => {
9 | let fields = tuple_fields(tuple);
10 | ts_string! {
11 | #fields
12 | }
13 | }
14 | TypeFieldsDefinition::Named(named) => {
15 | let fields = named_fields(named, inflection);
16 | ts_string! {
17 | { #fields }
18 | }
19 | }
20 | };
21 | ts_string! {
22 | export type #name = #fields;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/type_reflect/src/type_script/type_fields.rs:
--------------------------------------------------------------------------------
1 | use ts_quote::ts_string;
2 | use type_reflect_core::{Inflectable, Inflection, NamedField, Type};
3 |
4 | use crate::type_script::to_ts_type;
5 |
6 | pub fn named_member(member: &NamedField, inflection: Inflection) -> String {
7 | let name = &member.name.inflect(inflection);
8 |
9 | match &member.type_ {
10 | type_reflect_core::Type::Option(t) => {
11 | let value = to_ts_type(&t);
12 | format!("{name}?: {value};", name = name, value = value)
13 | }
14 | t => {
15 | let value = to_ts_type(&t);
16 | format!("{name}: {value};", name = name, value = value)
17 | }
18 | }
19 | }
20 |
21 | pub fn named_fields(fields: &Vec, inflection: Inflection) -> String {
22 | let members: Vec = fields
23 | .into_iter()
24 | .map(|field| named_member(field, inflection))
25 | .collect();
26 | members.join("\n ")
27 | }
28 |
29 | pub fn tuple_fields(fields: &Vec) -> String {
30 | if fields.len() == 1 {
31 | let type_ = to_ts_type(&fields[0]);
32 | ts_string! { #type_ }
33 | } else {
34 | let tuple_items: Vec = fields.into_iter().map(|item| to_ts_type(&item)).collect();
35 | let tuple_items = tuple_items.join(",\n ");
36 | ts_string! { [ #tuple_items ] }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/type_reflect/src/type_script/untagged_enum_type.rs:
--------------------------------------------------------------------------------
1 | use crate::EnumReflectionType;
2 | use ts_quote::ts_string;
3 | use type_reflect_core::{
4 | EnumCase, Inflectable, Inflection, NamedField, Type, TypeFieldsDefinition,
5 | };
6 |
7 | use super::{to_ts_type, type_fields, union_case_type_name};
8 |
9 | pub fn emit_untaggedd_enum_type() -> String
10 | where
11 | T: EnumReflectionType,
12 | {
13 | let name = T::name();
14 | let cases = T::cases();
15 | let inflection = T::inflection();
16 |
17 | let unit_cases: Vec<&EnumCase> = cases
18 | .iter()
19 | .filter(|c| {
20 | if let TypeFieldsDefinition::Unit = c.type_ {
21 | true
22 | } else {
23 | false
24 | }
25 | })
26 | .collect();
27 |
28 | let non_union_cases: Vec<&EnumCase> = cases
29 | .iter()
30 | .filter(|c| {
31 | if let TypeFieldsDefinition::Unit = c.type_ {
32 | false
33 | } else {
34 | true
35 | }
36 | })
37 | .collect();
38 |
39 | let unit_cases: Vec = unit_cases
40 | .iter()
41 | .map(|case| emit_unit_case(&case, inflection))
42 | .collect();
43 |
44 | let unit_cases: Option = if unit_cases.is_empty() {
45 | None
46 | } else {
47 | Some(unit_cases.join(" | "))
48 | };
49 |
50 | let member_cases: Vec = non_union_cases
51 | .iter()
52 | .map(|case| emit_member_case(&case, T::name(), inflection))
53 | .collect();
54 |
55 | let member_cases_block = if member_cases.is_empty() {
56 | None
57 | } else {
58 | let cases = member_cases.join(",\n");
59 | Some(ts_string! {
60 | {
61 | #cases
62 | }
63 | })
64 | };
65 |
66 | let member_case_types: Vec = non_union_cases
67 | .iter()
68 | .map(|case| emit_case_type(&case, T::name()))
69 | .collect();
70 | let member_case_types = member_case_types.join("\n");
71 |
72 | match (unit_cases, member_cases_block) {
73 | (None, None) => ts_string! {
74 | export type #name = never;
75 | },
76 | (None, Some(members)) => ts_string! {
77 | #member_case_types
78 |
79 | export type #name = #members;
80 | },
81 | (Some(units), None) => ts_string! {
82 | export type #name = #units;
83 | },
84 | (Some(units), Some(members)) => ts_string! {
85 | #member_case_types
86 |
87 | export type #name = #units | #members;
88 | },
89 | }
90 | }
91 |
92 | fn emit_unit_case(case: &EnumCase, inflection: Inflection) -> String {
93 | let name = &case.name.inflect(inflection);
94 | ts_string! { #"'#name'" }
95 | }
96 |
97 | fn emit_member_case(case: &EnumCase, parent_name: &str, inflection: Inflection) -> String {
98 | let name = &case.name.inflect(inflection);
99 | let member_type = emit_case_type_name(&case, parent_name);
100 | ts_string! { #name ? : #member_type }
101 | }
102 |
103 | pub fn emit_case_type_name(case: &EnumCase, parent_name: &str) -> String {
104 | match &case.type_ {
105 | TypeFieldsDefinition::Unit => unreachable!("unit cases don't have a a case type"),
106 | TypeFieldsDefinition::Tuple(items) => emit_tuple_case_type_name(&case, &items, parent_name),
107 | TypeFieldsDefinition::Named(named_fields) => union_case_type_name(case, parent_name),
108 | }
109 | }
110 |
111 | fn emit_tuple_case_type_name(
112 | case: &EnumCase,
113 | tuple_fields: &Vec,
114 | parent_name: &str,
115 | ) -> String {
116 | if let Some(field) = tuple_fields.first()
117 | && tuple_fields.len() == 1
118 | {
119 | to_ts_type(&field)
120 | } else {
121 | union_case_type_name(case, parent_name)
122 | }
123 | }
124 |
125 | fn emit_case_type(case: &EnumCase, parent_name: &str) -> String {
126 | let name = emit_case_type_name(case, parent_name);
127 | let contents = emit_case_type_contents(case, parent_name);
128 |
129 | if name == contents {
130 | return "".to_string();
131 | }
132 |
133 | ts_string! {
134 | export type #name = #contents;
135 | }
136 | }
137 |
138 | fn emit_case_type_contents(case: &EnumCase, parent_name: &str) -> String {
139 | match &case.type_ {
140 | TypeFieldsDefinition::Unit => unreachable!("unit cases don't have a a case type"),
141 | TypeFieldsDefinition::Tuple(items) => {
142 | emit_tuple_case_type_contentns(&case, &items, parent_name)
143 | }
144 | TypeFieldsDefinition::Named(named_fields) => {
145 | emit_struct_case_type_contentns(case, named_fields)
146 | }
147 | }
148 | }
149 |
150 | fn emit_tuple_case_type_contentns(
151 | case: &EnumCase,
152 | tuple_fields: &Vec,
153 | parent_name: &str,
154 | ) -> String {
155 | if let Some(field) = tuple_fields.first()
156 | && tuple_fields.len() == 1
157 | {
158 | to_ts_type(&field)
159 | } else {
160 | let members: Vec = tuple_fields
161 | .into_iter()
162 | .map(|field| to_ts_type(&field))
163 | .collect();
164 | let members = members.join(", ");
165 |
166 | ts_string! {
167 | [ #members ]
168 | }
169 | }
170 | }
171 |
172 | fn emit_struct_case_type_contentns(case: &EnumCase, named_fields: &Vec) -> String {
173 | let struct_items = type_fields::named_fields(named_fields, case.inflection);
174 |
175 | ts_string! {
176 | {
177 | #struct_items
178 | }
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/type_reflect/src/zod/alias_type.rs:
--------------------------------------------------------------------------------
1 | use crate::AliasType;
2 |
3 | use super::to_zod_type;
4 |
5 | pub fn emit_alias_type() -> String
6 | where
7 | T: AliasType,
8 | {
9 | return format!(
10 | r#"
11 |
12 | export const {name}Schema = {schema};
13 | export type {name} = z.infer;
14 |
15 | "#,
16 | name = T::name(),
17 | schema = to_zod_type(&T::source_type())
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/type_reflect/src/zod/enum_type.rs:
--------------------------------------------------------------------------------
1 | use type_reflect_core::{EnumCase, EnumType, Inflectable, Inflection};
2 |
3 | use crate::EnumReflectionType;
4 |
5 | use super::to_zod_type;
6 |
7 | pub fn emit_enum_type() -> String
8 | where
9 | T: EnumReflectionType,
10 | {
11 | match T::enum_type() {
12 | EnumType::Simple => emit_simple_enum_type::(),
13 | EnumType::Complex {
14 | case_key,
15 | content_key,
16 | } => emit_complex_enum_type::(&case_key, &content_key),
17 | EnumType::Untagged => unimplemented!("untagged enums not supported by the Zod generator"),
18 | }
19 | }
20 |
21 | fn emit_simple_enum_type() -> String
22 | where
23 | T: EnumReflectionType,
24 | {
25 | let simple_cases: String = T::cases()
26 | .into_iter()
27 | .map(|case| {
28 | format!(
29 | r#" {name} = "{name}",
30 | "#,
31 | name = case.name
32 | )
33 | })
34 | .collect();
35 |
36 | let schema_name = T::union_schema_name();
37 | let schema_cases: String = T::cases()
38 | .into_iter()
39 | .map(|case| {
40 | format!(
41 | " {enum_name}.{case_name},\n",
42 | enum_name = T::name(),
43 | case_name = case.name
44 | )
45 | })
46 | .collect();
47 |
48 | format!(
49 | r#"
50 | export enum {name} {{
51 | {simple_cases}}}
52 |
53 | export const {schema_name} = z.enum([
54 | {schema_cases}])
55 | "#,
56 | name = T::name(),
57 | simple_cases = simple_cases,
58 | schema_name = schema_name,
59 | schema_cases = schema_cases
60 | )
61 | }
62 |
63 | fn emit_complex_enum_type(case_key: &String, content_key: &Option) -> String
64 | where
65 | T: EnumReflectionType,
66 | {
67 | let cases_enum = T::generate_cases_enum();
68 | let union_types = T::generate_union_types(&case_key, &content_key, T::inflection());
69 | let union_type = T::generate_union_schema();
70 |
71 | // Generate case type
72 |
73 | // let members = enum_cases(&T::cases());
74 |
75 | format!(
76 | r#"
77 | {cases_enum}
78 | {union_types}
79 | {union_type}
80 | "#,
81 | cases_enum = cases_enum,
82 | union_types = union_types,
83 | union_type = union_type
84 | )
85 | }
86 |
87 | trait EnumTypeBridge: EnumReflectionType {
88 | fn case_type_name() -> String {
89 | format!("{}Case", Self::name())
90 | }
91 |
92 | fn case_id(case: &EnumCase) -> String {
93 | format!("{}.{}", Self::case_type_name(), case.name)
94 | }
95 |
96 | fn generate_cases_enum() -> String {
97 | let mut case_values = String::new();
98 | for case in Self::cases() {
99 | case_values.push_str(format!(r#" {name} = "{name}""#, name = case.name).as_str());
100 | case_values.push_str(",\n");
101 | }
102 |
103 | format!(
104 | r#"
105 | export enum {name} {{
106 | {cases}}}
107 | "#,
108 | name = Self::case_type_name(),
109 | cases = case_values
110 | )
111 | }
112 |
113 | fn generate_union_types(
114 | case_key: &String,
115 | content_key: &Option,
116 | inflection: Inflection,
117 | ) -> String {
118 | let mut result = String::new();
119 |
120 | for case in Self::cases() {
121 | result.push_str(
122 | Self::generate_union_type(&case, &case_key, &content_key, inflection).as_str(),
123 | )
124 | }
125 |
126 | result
127 | }
128 |
129 | fn generate_union_type(
130 | case: &EnumCase,
131 | case_key: &String,
132 | content_key: &Option,
133 | _inflection: Inflection,
134 | ) -> String {
135 | let schema_name = union_type_name(case, Self::name());
136 | let id = Self::case_id(case);
137 |
138 | let additional_fields = match &case.type_ {
139 | type_reflect_core::TypeFieldsDefinition::Unit => String::new(),
140 | type_reflect_core::TypeFieldsDefinition::Tuple(inner) => {
141 | let content_key = match content_key {
142 | Some(content_key) => content_key,
143 | None => {
144 | //TODO: make this a localized Syn error
145 | panic!("Content key required on enums containing at least one tuple-type variant.")
146 | }
147 | };
148 | if inner.len() == 1 {
149 | let type_ = to_zod_type(&inner[0]);
150 | format!(
151 | r#" {content_key}: {type_}"#,
152 | type_ = type_,
153 | content_key = content_key,
154 | )
155 | } else {
156 | let tuple_items: String = inner
157 | .into_iter()
158 | .map(|item| format!(" {},\n", to_zod_type(&item)))
159 | .collect();
160 |
161 | format!(
162 | r#" {content_key}: z.tuple([
163 | {tuple_items} ])"#,
164 | tuple_items = tuple_items,
165 | content_key = content_key,
166 | )
167 | }
168 | }
169 | type_reflect_core::TypeFieldsDefinition::Named(inner) => {
170 | let struct_items: String = inner
171 | .into_iter()
172 | .map(|item| {
173 | format!(
174 | " {}: {},\n",
175 | item.name.inflect(case.inflection),
176 | to_zod_type(&item.type_)
177 | )
178 | })
179 | .collect();
180 |
181 | match content_key {
182 | Some(content_key) => format!(
183 | r#" {content_key}: z.object({{
184 | {struct_items} }})"#,
185 | struct_items = struct_items,
186 | content_key = content_key,
187 | ),
188 | None => struct_items,
189 | }
190 | }
191 | };
192 | format!(
193 | r#"
194 | export const {schema_name} = z.object({{
195 | {case_key}: z.literal({id}),
196 | {additional_fields}}});
197 | export type {name} = z.infer
198 | "#,
199 | schema_name = schema_name,
200 | name = format!("{}Case{}", Self::name(), case.name),
201 | case_key = case_key,
202 | id = id,
203 | additional_fields = additional_fields
204 | )
205 | }
206 |
207 | fn union_schema_name() -> String {
208 | format!("{}Schema", Self::name())
209 | }
210 |
211 | fn generate_union_schema() -> String {
212 | let schema_name = Self::union_schema_name();
213 | let mut cases = String::new();
214 |
215 | for case in Self::cases() {
216 | cases.push_str(format!(" {},\n", union_type_name(&case, Self::name())).as_str());
217 | }
218 |
219 | format!(
220 | r#"
221 | export const {schema_name} = z.union([
222 | {cases}]);
223 | export type {name} = z.infer
224 | "#,
225 | cases = cases,
226 | schema_name = schema_name,
227 | name = Self::name()
228 | )
229 | }
230 | }
231 |
232 | fn union_type_name(case: &EnumCase, parent_name: &str) -> String {
233 | format!("{}Case{}Schema", parent_name, case.name)
234 | }
235 |
236 | impl EnumTypeBridge for T where T: EnumReflectionType {}
237 |
--------------------------------------------------------------------------------
/type_reflect/src/zod/mod.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::OsStr;
2 |
3 | pub use super::struct_type::*;
4 | pub use super::type_description::Type;
5 | use super::*;
6 |
7 | mod struct_type;
8 | use struct_type::*;
9 |
10 | mod enum_type;
11 | use enum_type::*;
12 |
13 | mod alias_type;
14 | use alias_type::*;
15 |
16 | #[derive(Default)]
17 | pub struct Zod {}
18 |
19 | pub trait TypeExporter {
20 | fn export() -> String;
21 | }
22 |
23 | fn to_zod_type(t: &Type) -> String {
24 | match t {
25 | // TODO: support generics
26 | Type::Named(t) => format!("{}Schema", t.name),
27 | Type::String => "z.string()".to_string(),
28 | Type::Int => "z.number()".to_string(),
29 | Type::UnsignedInt => "z.number()".to_string(),
30 | Type::Float => "z.number()".to_string(),
31 | Type::Boolean => "z.bool()".to_string(),
32 | Type::Option(t) => format!("{}.optional()", to_zod_type(t)),
33 | Type::Array(t) => format!("z.array({})", to_zod_type(t)),
34 | Type::Map { key, value } => format!("z.map({}, {})", to_zod_type(key), to_zod_type(value)),
35 | Type::Transparent(_t) => unimplemented!("Transparent types not yet implemented for Zod"),
36 | }
37 | }
38 |
39 | impl TypeEmitter for Zod {
40 | fn prefix(&mut self) -> String {
41 | "import { z } from 'zod';\n".to_string()
42 | }
43 |
44 | fn emit_struct(&mut self) -> String
45 | where
46 | T: StructType,
47 | {
48 | let members = struct_fields(&T::fields(), T::inflection());
49 | let name = T::name();
50 |
51 | format!(
52 | r#"
53 |
54 | export const {name}Schema = z.object({{
55 | {members}}});
56 |
57 | export type {name} = z.infer;
58 |
59 | "#,
60 | members = members,
61 | name = name
62 | )
63 | }
64 |
65 | fn emit_enum(&mut self) -> String
66 | where
67 | T: EnumReflectionType,
68 | {
69 | emit_enum_type::()
70 | }
71 |
72 | fn emit_alias(&mut self) -> String
73 | where
74 | T: AliasType,
75 | {
76 | emit_alias_type::()
77 | }
78 |
79 | fn finalize(&mut self, _path: P) -> Result<(), std::io::Error>
80 | where
81 | P: AsRef,
82 | {
83 | Ok(())
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/type_reflect/src/zod/struct_type.rs:
--------------------------------------------------------------------------------
1 | use crate::zod::to_zod_type;
2 | use ts_quote::*;
3 | use type_reflect_core::{Inflectable, Inflection, NamedField, TypeFieldsDefinition};
4 |
5 | pub fn struct_member(member: &NamedField, inflection: Inflection) -> String {
6 | let name = &member.name.inflect(inflection);
7 | let value = to_zod_type(&member.type_);
8 | ts_string! { #name: #value, }
9 |
10 | // format!(" {name}: {value},\n", name = name, value = value)
11 | }
12 |
13 | pub fn named_fields(fields: &Vec, inflection: Inflection) -> String {
14 | let mut result = String::new();
15 | for member in fields {
16 | result.push_str(struct_member(member, inflection).as_str())
17 | }
18 | result
19 | }
20 |
21 | pub fn struct_fields(fields: &TypeFieldsDefinition, inflection: Inflection) -> String {
22 | match fields {
23 | TypeFieldsDefinition::Unit => todo!(),
24 | TypeFieldsDefinition::Tuple(_) => todo!(),
25 | TypeFieldsDefinition::Named(named) => named_fields(named, inflection),
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/type_reflect/tests/.gitignore:
--------------------------------------------------------------------------------
1 | output/src/*
2 | output/node_modules/
3 |
--------------------------------------------------------------------------------
/type_reflect/tests/common.rs:
--------------------------------------------------------------------------------
1 | pub const OUTPUT_DIR: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/output");
2 |
3 | #[allow(unused)]
4 | pub const TESTING_PREFIX: &'static str = r#"
5 |
6 | function assertThrows(fn: ()=>void, message: string) {
7 | try {
8 | fn();
9 | } catch (e) {
10 | console.log(`error thrown: ${e}`);
11 | return;
12 | }
13 | throw new Error(message);
14 | }
15 |
16 | function assertDoesNotThrow(fn: ()=>T, message: string) {
17 | try {
18 | return fn();
19 | } catch (e) {
20 | console.error(message);
21 | throw e;
22 | }
23 | }
24 |
25 | "#;
26 |
27 | use std::{fs, path::PathBuf};
28 |
29 | use anyhow::{bail, Result};
30 | use std::process::Command;
31 |
32 | #[allow(unused)]
33 | pub struct OutputLocation {
34 | path: PathBuf,
35 | filename: String,
36 | }
37 |
38 | #[allow(unused)]
39 | fn run_command(dir: &str, command: &str) -> Result<()> {
40 | println!("Running command:\n\t{}", command);
41 |
42 | let mut parts = command.split_whitespace();
43 | let command = parts.next().expect("no command given");
44 | let args = parts.collect::>();
45 |
46 | let mut child = Command::new(command).args(&args).current_dir(dir).spawn()?; // Spawn the command as a child process
47 |
48 | let status = child.wait()?; // Wait for the command to complete
49 |
50 | if status.success() {
51 | } else {
52 | bail!("Command failed: {}", command)
53 | }
54 | Ok(())
55 | }
56 |
57 | #[allow(unused)]
58 | impl OutputLocation {
59 | pub fn ts_path(&self) -> PathBuf {
60 | self.path.with_extension("ts")
61 | }
62 | fn jest_path(&self) -> PathBuf {
63 | self.path.with_extension("test.ts")
64 | }
65 | fn clean(&self) {
66 | remove_file(self.ts_path());
67 | remove_file(self.jest_path());
68 | }
69 |
70 | pub fn run_ts(&self) -> Result<()> {
71 | println!("");
72 | run_command(
73 | OUTPUT_DIR,
74 | format!("yarn jest {}", self.jest_path().to_str().unwrap()).as_str(),
75 | )?;
76 | Ok(())
77 | }
78 |
79 | pub fn write_jest(&self, imports: &str, content: &str) -> Result<()> {
80 | fs::write(
81 | self.jest_path(),
82 | format!(
83 | "import {{ {imports} }} from './{file}'\n\n{content}",
84 | content = content,
85 | imports = imports,
86 | file = self.filename
87 | ),
88 | )?;
89 | Ok(())
90 | }
91 | }
92 |
93 | fn remove_file(path: PathBuf) {
94 | match fs::remove_file(&path) {
95 | Ok(_) => {}
96 | Err(e) => eprintln!("Error removing file: [{:?}]: {}", &path, e),
97 | }
98 | }
99 |
100 | pub fn init_path(scope: &str, name: &str) -> OutputLocation {
101 | let mut base_path: PathBuf = PathBuf::from(OUTPUT_DIR);
102 | base_path.push("src");
103 | base_path.push(scope);
104 | if !base_path.exists() {
105 | match fs::create_dir(&base_path) {
106 | Ok(_) => {}
107 | Err(e) => {
108 | eprintln!("Error creating directory [{:?}]: {}", &base_path, e);
109 | }
110 | }
111 | };
112 |
113 | base_path.push(name);
114 | let output = OutputLocation {
115 | path: base_path,
116 | filename: name.to_string(),
117 | };
118 | output.clean();
119 | output
120 | }
121 |
--------------------------------------------------------------------------------
/type_reflect/tests/output/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'node',
4 | // If you have a src folder where your TypeScript files reside
5 | roots: ['/src/'],
6 | };
7 |
--------------------------------------------------------------------------------
/type_reflect/tests/output/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "output",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "devDependencies": {
7 | "@types/jest": "^29.5.8",
8 | "jest": "^29.7.0",
9 | "ts-jest": "^29.1.1",
10 | "typescript": "^5.2.2"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/type_reflect/tests/output/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es5",
5 | "lib": [
6 | "es6"
7 | ],
8 | "strict": true,
9 | "esModuleInterop": true,
10 | },
11 | "include": [
12 | "src/**/*"
13 | ],
14 | "exclude": [
15 | "node_modules",
16 | "dist"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/type_reflect/tests/test_adt.rs:
--------------------------------------------------------------------------------
1 | mod common;
2 |
3 | use anyhow::Result;
4 | use common::*;
5 |
6 | use serde::{Deserialize, Serialize};
7 | use type_reflect::*;
8 |
9 | #[derive(Reflect, Serialize, Deserialize)]
10 | pub struct Rectangle {
11 | width: f32,
12 | height: f32,
13 | }
14 |
15 | #[derive(Reflect, Serialize, Deserialize)]
16 | #[serde(tag = "_case", content = "data")]
17 | pub enum Shape {
18 | Circle { radius: f32 },
19 | Square { side: f32 },
20 | Rectangle(Rectangle),
21 | ScaledRectangle(Rectangle, u32),
22 | Null,
23 | }
24 |
25 | pub const SCOPE: &'static str = "test_adt";
26 |
27 | #[test]
28 | fn test_validation() -> Result<()> {
29 | let output = init_path(SCOPE, "test_validation");
30 |
31 | export_types!(
32 | types: [ Shape, Rectangle ],
33 | destinations: [(
34 | output.ts_path(),
35 | emitters: [
36 | TypeScript(),
37 | TSValidation(),
38 | TSFormat(
39 | tab_size: 2,
40 | line_width: 80,
41 | ),
42 | ],
43 | )]
44 | )?;
45 |
46 | output.write_jest(
47 | "Shape, Rectangle, ShapeCase, ShapeCaseKey",
48 | ts_string! {
49 | describe("ADT Validation", ()=>{
50 | it("Validates a Null variant: ShapeCaseKey.Null", ()=>{
51 | expect(() => {
52 | Shape.validate({_case: ShapeCaseKey.Null})
53 | }).not.toThrow();
54 | });
55 | it("Validates a Null variant literal: 'Null'", ()=>{
56 | expect(() => {
57 | Shape.validate({_case: "Null"})
58 | }).not.toThrow();
59 | });
60 | it("Validates a Circle variant: {_case: ShapeCaseKey.Circle, data: { radius: 1.7} }", ()=>{
61 | expect(() => {
62 | Shape.validate({
63 | _case: ShapeCaseKey.Circle,
64 | data: {
65 | radius: 1.7
66 | }
67 | })
68 | }).not.toThrow();
69 | });
70 | it("Validates a Rectangle variant: {_case: ShapeCaseKey.Rectangle, data: { width: 1, height: 2} }", ()=>{
71 | expect(() => {
72 | Shape.validate({
73 | _case: ShapeCaseKey.Rectangle,
74 | data: {
75 | width: 1,
76 | height: 2
77 | }
78 | })
79 | }).not.toThrow();
80 | });
81 | it("Validates a ScaledRectangle variant: {_case: ShapeCaseKey.ScaledRectangle, data: [{ width: 1, height: 2}, 0.5] }", ()=>{
82 | expect(() => {
83 | Shape.validate({
84 | _case: ShapeCaseKey.ScaledRectangle,
85 | data: [
86 | {
87 | width: 1,
88 | height: 2
89 | },
90 | 0.5
91 | ]
92 | })
93 | }).not.toThrow();
94 | });
95 | it("Doesn't Validate an incorrect ScaledRectangle variant: {_case: ShapeCaseKey.Circle, data: [{ width: 1, height: 2}, 0.5] }", ()=>{
96 | expect(() => {
97 | Shape.validate({
98 | _case: ShapeCaseKey.Circle,
99 | data: [
100 | {
101 | width: 1,
102 | height: 2
103 | },
104 | 0.5
105 | ]
106 | })
107 | }).toThrow();
108 | });
109 |
110 | });
111 | }
112 | .as_str(),
113 | )?;
114 |
115 | output.run_ts()
116 | }
117 |
--------------------------------------------------------------------------------
/type_reflect/tests/test_array.rs:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/type_reflect/tests/test_boxed.rs:
--------------------------------------------------------------------------------
1 | mod common;
2 |
3 | use anyhow::Result;
4 | use common::*;
5 |
6 | use serde::{Deserialize, Serialize};
7 | use ts_quote::*;
8 | use type_reflect::*;
9 |
10 | pub const SCOPE: &'static str = "test_boxed";
11 |
12 | #[derive(Reflect, Serialize, Deserialize)]
13 | pub struct Bar {
14 | val: bool,
15 | }
16 |
17 | #[derive(Reflect, Serialize, Deserialize)]
18 | pub struct BoxOfPrimitive {
19 | boxed: Box,
20 | // boxed: u32,
21 | }
22 |
23 | #[derive(Reflect, Serialize, Deserialize)]
24 | pub struct BoxOfType {
25 | boxed: Box,
26 | }
27 |
28 | #[test]
29 | fn test_box_of_primitive() -> Result<()> {
30 | let output = init_path(SCOPE, "test_box_of_primitive");
31 |
32 | export_types!(
33 | types: [ BoxOfPrimitive ],
34 | destinations: [(
35 | output.ts_path(),
36 | emitters: [
37 | TypeScript(),
38 | TSValidation(),
39 | TSFormat(
40 | tab_size: 2,
41 | line_width: 80,
42 | ),
43 | ],
44 | )]
45 | )?;
46 |
47 | output.write_jest(
48 | "BoxOfPrimitive",
49 | r#"
50 |
51 | describe('Box of Primitive Validation', ()=>{
52 |
53 | it('validates an object: `{ boxed: 42 }` which conforms to BoxOfPrimitive', ()=>{
54 | expect(() => {
55 | BoxOfPrimitive.validate({ boxed: 42 });
56 | }).not.toThrow();
57 | });
58 |
59 | it('throws an error validating a malformed object: `{ boxed: [42] }`', ()=>{
60 | expect(() => {
61 | BoxOfPrimitive.validate({ boxed: [42] })
62 | }).toThrow();
63 | });
64 |
65 | })
66 | "#,
67 | )?;
68 |
69 | output.run_ts()
70 | }
71 |
72 | #[test]
73 | fn test_box_of_type() -> Result<()> {
74 | let output = init_path(SCOPE, "test_box_of_type");
75 |
76 | export_types!(
77 | types: [ Bar, BoxOfType ],
78 | destinations: [(
79 | output.ts_path(),
80 | emitters: [
81 | TypeScript(),
82 | TSValidation(),
83 | TSFormat(
84 | tab_size: 2,
85 | line_width: 80,
86 | ),
87 | ],
88 | )]
89 | )?;
90 |
91 | output.write_jest(
92 | "Bar, BoxOfType",
93 | r#"
94 |
95 | describe('Box of Type Validation', ()=>{
96 |
97 | it('validates an object: `{ boxed: { val: true } }` which conforms to BoxOfType', ()=>{
98 | expect(() => {
99 | BoxOfType.validate({ boxed: { val: true } });
100 | }).not.toThrow();
101 | });
102 |
103 | it('throws an error validating a malformed object: `{ boxed: [true] }`', ()=>{
104 | expect(() => {
105 | BoxOfType.validate({ boxed: [true] })
106 | }).toThrow();
107 | });
108 |
109 | })
110 | "#,
111 | )?;
112 |
113 | output.run_ts()
114 | }
115 |
116 | #[derive(Reflect, Serialize, Deserialize)]
117 | #[serde(tag = "_case", content = "data")]
118 | pub enum Tree {
119 | Leaf(bool),
120 | Subtree { left: Box, right: Box },
121 | }
122 |
123 | #[test]
124 | fn test_nested_box() -> Result<()> {
125 | let output = init_path(SCOPE, "test_nested_box");
126 |
127 | export_types!(
128 | types: [ Tree ],
129 | destinations: [(
130 | output.ts_path(),
131 | emitters: [
132 | TypeScript(),
133 | TSValidation(),
134 | TSFormat(
135 | tab_size: 2,
136 | line_width: 80,
137 | ),
138 | ],
139 | )]
140 | )?;
141 |
142 | output.write_jest(
143 | "Tree, TreeCase, TreeCaseKey",
144 | ts_quote! {
145 |
146 | describe("Nested Box Validation", ()=>{
147 |
148 | it("validates an object: `{ ... }` which conforms to Tree", ()=>{
149 | expect(() => {
150 | Tree.validate(
151 | {
152 | _case: TreeCaseKey.Subtree,
153 | data: {
154 | left: { _case: TreeCaseKey.Leaf, data: true },
155 | right: {
156 | _case: TreeCaseKey.Subtree,
157 | data: {
158 | left: { _case: TreeCaseKey.Leaf, data: true },
159 | right: { _case: TreeCaseKey.Leaf, data: false }
160 | }
161 | }
162 | }
163 | }
164 | );
165 | }).not.toThrow();
166 | });
167 |
168 | it("throws an error validating a malformed object: `{ boxed: [true] }`", ()=>{
169 | expect(() => {
170 | Tree.validate({ boxed: [true] })
171 | }).toThrow();
172 | });
173 |
174 | })
175 |
176 |
177 | }?
178 | .formatted(None)?
179 | .as_str(),
180 | )?;
181 |
182 | output.run_ts()
183 | }
184 |
--------------------------------------------------------------------------------
/type_reflect/tests/test_map.rs:
--------------------------------------------------------------------------------
1 | mod common;
2 |
3 | use std::collections::{BTreeMap, HashMap};
4 |
5 | use anyhow::Result;
6 | use common::*;
7 |
8 | use serde::{Deserialize, Serialize};
9 | use type_reflect::*;
10 |
11 | pub const SCOPE: &'static str = "test_map";
12 |
13 | #[derive(Reflect, Serialize, Deserialize)]
14 | pub struct Bar {
15 | val: bool,
16 | }
17 |
18 | #[derive(Reflect, Serialize, Deserialize)]
19 | pub struct MapOfPrimitive {
20 | records: HashMap,
21 | }
22 |
23 | #[derive(Reflect, Serialize, Deserialize)]
24 | pub struct MapOfType {
25 | records: HashMap,
26 | }
27 |
28 | #[derive(Reflect, Serialize, Deserialize)]
29 | pub struct BTreeBasedMap {
30 | records: BTreeMap,
31 | }
32 |
33 | #[test]
34 | fn test_map_of_primitive() -> Result<()> {
35 | let output = init_path(SCOPE, "test_map_of_primitive");
36 |
37 | export_types!(
38 | types: [ MapOfPrimitive ],
39 | destinations: [(
40 | output.ts_path(),
41 | emitters: [
42 | TypeScript(),
43 | TSValidation(),
44 | TSFormat(
45 | tab_size: 2,
46 | line_width: 80,
47 | ),
48 | ],
49 | )]
50 | )?;
51 |
52 | output.write_jest(
53 | "MapOfPrimitive",
54 | r#"
55 |
56 | describe('Struct with Array of Primitives Validation', ()=>{
57 |
58 | it('validates an object: `{ records: {a: 42, b: 7, dog: 3, cat: 21} }` which conforms to MapOfPrimitive', ()=>{
59 | expect(() => {
60 | MapOfPrimitive.validate({ records: {a: 42, b: 7, dog: 3, cat: 21} });
61 | }).not.toThrow();
62 | });
63 |
64 | it('validates an empty array: `{ records: {} }` which conforms to MapOfPrimitive', ()=>{
65 | expect(() => {
66 | MapOfPrimitive.validate({ records: {} });
67 | }).not.toThrow();
68 | });
69 |
70 | it('throws an error validating an object: `{a: 42, b: 7, dog: "3", cat: 21}` which has one value not conforming to the type', ()=>{
71 | expect(() => {
72 | MapOfPrimitive.validate({a: 42, b: 7, dog: "3", cat: 21})
73 | }).toThrow();
74 | });
75 |
76 | })
77 | "#,
78 | )?;
79 |
80 | output.run_ts()
81 | }
82 |
83 | #[test]
84 | fn test_nested_map() -> Result<()> {
85 | let output = init_path(SCOPE, "test_nested_map");
86 |
87 | export_types!(
88 | types: [ MapOfType, Bar ],
89 | destinations: [(
90 | output.ts_path(),
91 | emitters: [
92 | TypeScript(),
93 | TSValidation(),
94 | TSFormat(
95 | tab_size: 2,
96 | line_width: 80,
97 | ),
98 | ],
99 | )]
100 | )?;
101 |
102 | output.write_jest(
103 | "MapOfType, Bar",
104 | r#"
105 |
106 | describe('Struct with Map of Types Validation', ()=>{
107 |
108 | it('validates an object: `{ records: {a: { val: true }, b: {val: false } } }` which conforms to MapOfType', ()=>{
109 | expect(() => {
110 | MapOfType.validate({ records: {a: { val: true }, b: {val: false } } });
111 | }).not.toThrow();
112 | });
113 |
114 | it('validates an empty object: `{ records: {} }` which conforms to MapOfType', ()=>{
115 | expect(() => {
116 | MapOfType.validate({ records: {} });
117 | }).not.toThrow();
118 | });
119 |
120 | it('throws an error validating an object: `{ records: {a: { val: true }, b: {val: false }, c: 32 } }` which has one value not conforming to the type', ()=>{
121 | expect(() => {
122 | MapOfType.validate({ records: {a: { val: true }, b: {val: false }, c: 32 } })
123 | }).toThrow();
124 | });
125 |
126 | })
127 | "#,
128 | )?;
129 |
130 | output.run_ts()
131 | }
132 |
133 | #[test]
134 | fn test_btree_baseed_map() -> Result<()> {
135 | let output = init_path(SCOPE, "test_btree_baseed_map");
136 |
137 | export_types!(
138 | types: [ BTreeBasedMap ],
139 | destinations: [(
140 | output.ts_path(),
141 | emitters: [
142 | TypeScript(),
143 | TSValidation(),
144 | TSFormat(
145 | tab_size: 2,
146 | line_width: 80,
147 | ),
148 | ],
149 | )]
150 | )?;
151 |
152 | output.write_jest(
153 | "BTreeBasedMap",
154 | r#"
155 |
156 | describe('Struct with Array of Primitives Validation', ()=>{
157 |
158 | it('validates an object: `{ records: {a: 42, b: 7, dog: 3, cat: 21} }` which conforms to BTreeBasedMap', ()=>{
159 | expect(() => {
160 | BTreeBasedMap.validate({ records: {a: 42, b: 7, dog: 3, cat: 21} });
161 | }).not.toThrow();
162 | });
163 |
164 | it('validates an empty array: `{ records: {} }` which conforms to BTreeBasedMap', ()=>{
165 | expect(() => {
166 | BTreeBasedMap.validate({ records: {} });
167 | }).not.toThrow();
168 | });
169 |
170 | it('throws an error validating an object: `{a: 42, b: 7, dog: "3", cat: 21}` which has one value not conforming to the type', ()=>{
171 | expect(() => {
172 | BTreeBasedMap.validate({a: 42, b: 7, dog: "3", cat: 21})
173 | }).toThrow();
174 | });
175 |
176 | })
177 | "#,
178 | )?;
179 |
180 | output.run_ts()
181 | }
182 |
--------------------------------------------------------------------------------
/type_reflect/tests/test_nested.rs:
--------------------------------------------------------------------------------
1 | mod common;
2 |
3 | use anyhow::Result;
4 | use common::*;
5 |
6 | use serde::{Deserialize, Serialize};
7 | use type_reflect::*;
8 |
9 | pub const SCOPE: &'static str = "test_nested";
10 |
11 | #[derive(Reflect, Serialize, Deserialize)]
12 | pub struct Bar {
13 | val: bool,
14 | }
15 |
16 | #[derive(Reflect, Serialize, Deserialize)]
17 | pub struct Foo {
18 | bar: Bar,
19 | }
20 |
21 | #[test]
22 | fn test_validation() -> Result<()> {
23 | let output = init_path(SCOPE, "test_validation");
24 |
25 | export_types!(
26 | types: [ Foo, Bar ],
27 | destinations: [(
28 | output.ts_path(),
29 | emitters: [
30 | TypeScript(),
31 | TSValidation(),
32 | TSFormat(
33 | tab_size: 2,
34 | line_width: 80,
35 | ),
36 | ],
37 | )]
38 | )?;
39 |
40 | output.write_jest(
41 | "Foo, Bar",
42 | r#"
43 |
44 | describe('Struct with Nested Type Validation', ()=>{
45 |
46 | it('validates an object: `{ bar: { val: true } }` which conforms to the nested types', ()=>{
47 | expect(() => {
48 | Foo.validate({ bar: { val: true } });
49 | }).not.toThrow();
50 | });
51 |
52 | it('throws an error validating an object: `{ bar: { val: "hola" } }` not conforming to the nested type', ()=>{
53 | expect(() => {
54 | Foo.validate({ bar: { val: "hola" } })
55 | }).toThrow();
56 | });
57 |
58 | it('throws an error validating an object: `{ bar: true }` not conforming to the nested type', ()=>{
59 | expect(() => {
60 | Foo.validate({ bar: true })
61 | }).toThrow();
62 | });
63 |
64 | it('throws an error validating an object: `{ baz: { val: true } }` not conforming to the outer type', ()=>{
65 | expect(() => {
66 | Foo.validate({ baz: { val: true } })
67 | }).toThrow();
68 | });
69 |
70 | })
71 | "#,
72 | )?;
73 |
74 | output.run_ts()
75 | }
76 |
--------------------------------------------------------------------------------
/type_reflect/tests/test_optional.rs:
--------------------------------------------------------------------------------
1 | mod common;
2 |
3 | use anyhow::Result;
4 | use common::*;
5 |
6 | use serde::{Deserialize, Serialize};
7 | use type_reflect::*;
8 |
9 | #[derive(Reflect, Serialize, Deserialize)]
10 | pub struct Foo {
11 | pub x: f32,
12 | pub y: Option,
13 | }
14 |
15 | pub const SCOPE: &'static str = "test_optional";
16 |
17 | #[test]
18 | fn test_validation() -> Result<()> {
19 | let output = init_path(SCOPE, "test_validation");
20 |
21 | export_types!(
22 | types: [ Foo ],
23 | destinations: [(
24 | output.ts_path(),
25 | emitters: [
26 | TypeScript(),
27 | TSValidation(),
28 | TSFormat(
29 | tab_size: 2,
30 | line_width: 80,
31 | ),
32 | ],
33 | )]
34 | )?;
35 |
36 | output.write_jest(
37 | "Foo",
38 | r#"
39 |
40 | describe('Struct with Optional Member Validation', ()=>{
41 |
42 | it("validates an object: `{x: 7, y: 42}` with both the requred and optional members", ()=>{
43 | expect(() => {
44 | Foo.validate({x: 7, y: 42})
45 | }).not.toThrow();
46 | });
47 |
48 | it("validates an object: `{x: 7}` matching the type `Foo` without the optional member `y`", ()=>{
49 | expect(() => {
50 | Foo.validate({x: 7})
51 | }).not.toThrow();
52 | });
53 |
54 | it("throws an error validating an object: `{y: 42}` missing the required member `x`", ()=>{
55 | expect(() => {
56 | Foo.validate({y: 42})
57 | }).toThrow();
58 | });
59 |
60 | })
61 | "#,
62 | )?;
63 |
64 | output.run_ts()
65 | }
66 |
--------------------------------------------------------------------------------
/type_reflect/tests/test_simple_enum.rs:
--------------------------------------------------------------------------------
1 | mod common;
2 |
3 | use anyhow::Result;
4 | use common::*;
5 |
6 | use serde::{Deserialize, Serialize};
7 | use type_reflect::*;
8 |
9 | #[derive(Reflect, Serialize, Deserialize)]
10 | pub enum Pet {
11 | Dog,
12 | Cat,
13 | }
14 |
15 | pub const SCOPE: &'static str = "test_simple_enum";
16 |
17 | #[test]
18 | fn test_validation() -> Result<()> {
19 | let output = init_path(SCOPE, "test_validation");
20 |
21 | export_types!(
22 | types: [ Pet ],
23 | destinations: [(
24 | output.ts_path(),
25 | emitters: [
26 | TypeScript(),
27 | TSValidation(),
28 | TSFormat(
29 | tab_size: 2,
30 | line_width: 80,
31 | ),
32 | ],
33 | )]
34 | )?;
35 |
36 | output.write_jest(
37 | "Pet",
38 | r#"
39 |
40 | describe('Simple Enum Validation', ()=>{
41 |
42 | it("validates an object: `Dog`", ()=>{
43 | expect(() => {
44 | Pet.validate(`Dog`)
45 | }).not.toThrow();
46 | });
47 |
48 | it("validates an object: `Cat`", ()=>{
49 | expect(() => {
50 | Pet.validate(`Cat`)
51 | }).not.toThrow();
52 | });
53 |
54 | it("throws an error validating an number: `7`", ()=>{
55 | expect(() => {
56 | Pet.validate(7)
57 | }).toThrow();
58 | });
59 |
60 | it("throws an error validating an object: `{tag: 'Dog'}`", ()=>{
61 | expect(() => {
62 | Pet.validate({tag: 'Dog'})
63 | }).toThrow();
64 | });
65 |
66 | })
67 | "#,
68 | )?;
69 |
70 | output.run_ts()
71 | }
72 |
--------------------------------------------------------------------------------
/type_reflect/tests/test_simple_struct.rs:
--------------------------------------------------------------------------------
1 | mod common;
2 |
3 | use anyhow::Result;
4 | use common::*;
5 |
6 | use serde::{Deserialize, Serialize};
7 | use type_reflect::*;
8 |
9 | #[derive(Reflect, Serialize, Deserialize)]
10 | pub struct Foo {
11 | pub x: f32,
12 | }
13 |
14 | pub const SCOPE: &'static str = "test_simple_struct";
15 |
16 | #[test]
17 | fn test_validation() -> Result<()> {
18 | let output = init_path(SCOPE, "test_validation");
19 |
20 | export_types!(
21 | types: [ Foo ],
22 | destinations: [(
23 | output.ts_path(),
24 | emitters: [
25 | TypeScript(),
26 | TSValidation(),
27 | TSFormat(
28 | tab_size: 2,
29 | line_width: 80,
30 | ),
31 | ],
32 | )]
33 | )?;
34 |
35 | output.write_jest(
36 | "Foo",
37 | r#"
38 |
39 | describe('Simple Struct Validation', ()=>{
40 | it("validates an object: `{x: 7}` matching the type `Foo` without throwing", ()=>{
41 | expect(() => {
42 | Foo.validate({x: 7})
43 | }).not.toThrow();
44 | });
45 |
46 | it("validates an object: `{x: -7}` matching the type `Foo` without throwing", ()=>{
47 | expect(() => {
48 | Foo.validate({x: -7})
49 | }).not.toThrow();
50 | });
51 |
52 | it("validates an object: `{x: 0}` matching the type `Foo` without throwing", ()=>{
53 | expect(() => {
54 | Foo.validate({x: 0})
55 | }).not.toThrow();
56 | });
57 |
58 | it("throws an error validating an object: `{x: '7'}` not matching the type `Foo`", ()=>{
59 | expect(() => {
60 | Foo.validate({x: '7'})
61 | }).toThrow();
62 | });
63 |
64 | it("throws an error validating an object: `{y: 7}` not matching the type `Foo`", ()=>{
65 | expect(() => {
66 | Foo.validate({y: 7})
67 | }).toThrow();
68 | });
69 |
70 | it("throws an error validating a string: `foo` not matching the type `Foo`", ()=>{
71 | expect(() => {
72 | Foo.validate('foo')
73 | }).toThrow();
74 | });
75 |
76 | it("throws an error validating a number: 7 not matching the type `Foo`", ()=>{
77 | expect(() => {
78 | Foo.validate(7)
79 | }).toThrow();
80 | });
81 |
82 | it("throws an error validating a boolean: false not matching the type `Foo`", ()=>{
83 | expect(() => {
84 | Foo.validate(false)
85 | }).toThrow();
86 | });
87 | })
88 | "#,
89 | )?;
90 |
91 | output.run_ts()
92 | }
93 |
94 | #[test]
95 | fn test_parsing() -> Result<()> {
96 | let output = init_path(SCOPE, "test_parsing");
97 |
98 | export_types!(
99 | types: [ Foo ],
100 | destinations: [(
101 | output.ts_path(),
102 | // prefix: TESTING_PREFIX,
103 | // postfix: post,
104 | emitters: [
105 | TypeScript(),
106 | TSValidation(),
107 | TSFormat(
108 | tab_size: 2,
109 | line_width: 80,
110 | ),
111 | ],
112 | )]
113 | )?;
114 |
115 | output.write_jest(
116 | "Foo",
117 | r#"
118 |
119 | describe('Simple Struct Parsing', ()=>{
120 | it('parses json: `{"x": 7}` matching the type `Foo` without throwing', ()=>{
121 | expect(() => {
122 | const foo = Foo.parse(`{"x": 7}`);
123 | expect(foo.x).toBe(7);
124 | }).not.toThrow();
125 | });
126 | it('parses json: `{"x": -7}` matching the type `Foo` without throwing', ()=>{
127 | expect(() => {
128 | const foo = Foo.parse(`{"x": -7}`);
129 | expect(foo.x).toBe(-7);
130 | }).not.toThrow();
131 | });
132 | it('parses json: `{"x": 0}` matching the type `Foo` without throwing', ()=>{
133 | expect(() => {
134 | const foo = Foo.parse(`{"x": 0}`);
135 | expect(foo.x).toBe(0);
136 | }).not.toThrow();
137 | });
138 | it('parses json: `{"x": 3.14159}` matching the type `Foo` without throwing', ()=>{
139 | expect(() => {
140 | const foo = Foo.parse(`{"x": 3.14159}`);
141 | expect(foo.x).toBe(3.14159);
142 | }).not.toThrow();
143 | });
144 | it('throws an error parsing a string: `{"y": 7}` not matching the type `Foo`', ()=>{
145 | expect(() => {
146 | Foo.parse(`{"y": 7}`)
147 | }).toThrow();
148 | });
149 | it('throws an error parsing a string: `qewcm9823d` not matching the type `Foo`', ()=>{
150 | expect(() => {
151 | Foo.parse(`qewcm9823d`)
152 | }).toThrow();
153 | });
154 | it('throws an error parsing an invalid json string: `{x: 7}` not matching the type `Foo`', ()=>{
155 | expect(() => {
156 | Foo.parse(`{x: 7}`)
157 | }).toThrow();
158 | });
159 | })
160 | "#,
161 | )?;
162 |
163 | output.run_ts()
164 | }
165 |
--------------------------------------------------------------------------------
/type_reflect/tests/test_struct_types.rs:
--------------------------------------------------------------------------------
1 | mod common;
2 |
3 | use anyhow::Result;
4 | use common::*;
5 |
6 | use serde::{Deserialize, Serialize};
7 | use serde_json;
8 | use type_reflect::*;
9 |
10 | #[derive(Reflect, Serialize, Deserialize)]
11 | pub struct Named {
12 | pub x: u32,
13 | }
14 |
15 | #[derive(Reflect, Serialize, Deserialize)]
16 | pub struct Tuple(u32);
17 |
18 | #[derive(Reflect, Serialize, Deserialize)]
19 | pub struct MultiTuple(u32, Named, Tuple);
20 |
21 | pub const SCOPE: &'static str = "test_struct_types";
22 |
23 | #[test]
24 | fn test_named() -> Result<()> {
25 | let output = init_path(SCOPE, "test_named");
26 |
27 | let named = Named { x: 42 };
28 | let serialized = serde_json::to_string_pretty(&named)?;
29 | eprintln!("Serialzied named: {}", serialized);
30 |
31 | export_types!(
32 | types: [ Named ],
33 | destinations: [(
34 | output.ts_path(),
35 | emitters: [
36 | TypeScript(),
37 | TSValidation(),
38 | TSFormat(
39 | tab_size: 2,
40 | line_width: 80,
41 | ),
42 | ],
43 | )]
44 | )?;
45 | output.write_jest(
46 | "Named",
47 | ts_string! {
48 | describe("Named Validation", ()=>{
49 | it("Validates a valid named striuct", ()=>{
50 | expect(() => {
51 | Named.validate({x:42})
52 | }).not.toThrow();
53 | });
54 | });
55 | }
56 | .as_str(),
57 | )?;
58 |
59 | output.run_ts()
60 | }
61 |
62 | #[test]
63 | fn test_tuple() -> Result<()> {
64 | let output = init_path(SCOPE, "test_tuple");
65 |
66 | let tuple = Tuple(42);
67 | let serialized = serde_json::to_string_pretty(&tuple)?;
68 | eprintln!("Serialzied tuple: {}", serialized);
69 |
70 | export_types!(
71 | types: [ Tuple ],
72 | destinations: [(
73 | output.ts_path(),
74 | emitters: [
75 | TypeScript(),
76 | TSValidation(),
77 | TSFormat(
78 | tab_size: 2,
79 | line_width: 80,
80 | ),
81 | ],
82 | )]
83 | )?;
84 |
85 | output.write_jest(
86 | "Tuple",
87 | ts_string! {
88 | describe("Tuple Validation", ()=>{
89 | it("Validates a valid tuple", ()=>{
90 | expect(() => {
91 | Tuple.validate(42)
92 | }).not.toThrow();
93 | });
94 | });
95 | }
96 | .as_str(),
97 | )?;
98 |
99 | output.run_ts()
100 | }
101 |
102 | #[test]
103 | fn test_multi_tuple() -> Result<()> {
104 | let output = init_path(SCOPE, "test_multi_tuple");
105 |
106 | let tuple = MultiTuple(42, Named { x: 42 }, Tuple(7));
107 | let serialized = serde_json::to_string_pretty(&tuple)?;
108 | eprintln!("Serialzied tuple: {}", serialized);
109 |
110 | export_types!(
111 | types: [ Named, Tuple, MultiTuple ],
112 | destinations: [(
113 | output.ts_path(),
114 | emitters: [
115 | TypeScript(),
116 | TSValidation(),
117 | TSFormat(
118 | tab_size: 2,
119 | line_width: 80,
120 | ),
121 | ],
122 | )]
123 | )?;
124 |
125 | output.write_jest(
126 | "Named, Tuple, MultiTuple",
127 | ts_string! {
128 | describe("MultiTuple Validation", ()=>{
129 | it("Validates a valid multi-tuple", ()=>{
130 | expect(() => {
131 | MultiTuple.validate([
132 | 42,
133 | { x: 7 },
134 | 99
135 | ])
136 | }).not.toThrow();
137 | });
138 | });
139 | }
140 | .as_str(),
141 | )?;
142 |
143 | output.run_ts()
144 | }
145 |
--------------------------------------------------------------------------------
/type_reflect/tests/test_ts_quote.rs:
--------------------------------------------------------------------------------
1 | mod common;
2 |
3 | use anyhow::Result;
4 | use common::*;
5 |
6 | use ts_quote::ts_quote;
7 | use ts_quote::{TSSource, TS};
8 | use type_reflect::*;
9 |
10 | pub const SCOPE: &'static str = "test_ts_quote";
11 |
12 | #[test]
13 | fn test_ident_substitution() -> Result<()> {
14 | let output = init_path(SCOPE, "test_ident_substitution");
15 |
16 | let hola = 7;
17 | let foo = 3;
18 | let bar = 4;
19 |
20 | let ts: TS = ts_quote! {
21 | const val = #hola + #{foo + bar};
22 | const lemon = #"`egg salad sandwich ${val}`";
23 | const peas = #"`egg salad sandwich ${val} == #foo`";
24 | const soup = #"`egg salad sandwich ${val} == #{foo - bar} something something`";
25 | }?;
26 |
27 | let prefix = ts.formatted(None)?;
28 |
29 | export_types!(
30 | types: [],
31 | destinations: [(
32 | output.ts_path(),
33 | prefix: prefix,
34 | emitters: [
35 | TSFormat(
36 | tab_size: 2,
37 | line_width: 80,
38 | ),
39 | ],
40 | )]
41 | )?;
42 | Ok(())
43 | }
44 |
--------------------------------------------------------------------------------
/type_reflect/tests/test_ts_string.rs:
--------------------------------------------------------------------------------
1 | mod common;
2 |
3 | use anyhow::Result;
4 | use common::*;
5 |
6 | use ts_quote::ts_string;
7 | use type_reflect::*;
8 |
9 | pub const SCOPE: &'static str = "test_ts_string";
10 |
11 | #[test]
12 | fn test_ts_str() -> Result<()> {
13 | let output = init_path(SCOPE, "test_ts_string");
14 |
15 | let prefix = ts_string! {
16 | const x = 4;
17 | };
18 |
19 | export_types!(
20 | types: [ ],
21 | destinations: [(
22 | output.ts_path(),
23 | prefix: prefix,
24 | emitters: [
25 | TSFormat(
26 | tab_size: 2,
27 | line_width: 80,
28 | ),
29 | ],
30 | )]
31 | )?;
32 |
33 | Ok(())
34 | }
35 |
36 | #[test]
37 | fn test_with_str() -> Result<()> {
38 | let output = init_path(SCOPE, "test_with_str");
39 |
40 | let prefix = ts_string! {
41 | const x = "Foo";
42 | };
43 |
44 | export_types!(
45 | types: [ ],
46 | destinations: [(
47 | output.ts_path(),
48 | prefix: prefix,
49 | emitters: [
50 | TSFormat(
51 | tab_size: 2,
52 | line_width: 80,
53 | ),
54 | ],
55 | )]
56 | )?;
57 |
58 | Ok(())
59 | }
60 |
61 | #[test]
62 | fn test_groups() -> Result<()> {
63 | let output = init_path(SCOPE, "test_groups");
64 |
65 | let prefix = ts_string! {
66 | const double = (x: number): number => {
67 | return x * 2;
68 | }
69 | };
70 |
71 | export_types!(
72 | types: [ ],
73 | destinations: [(
74 | output.ts_path(),
75 | prefix: prefix,
76 | emitters: [
77 | TSFormat(
78 | tab_size: 2,
79 | line_width: 80,
80 | ),
81 | ],
82 | )]
83 | )?;
84 |
85 | Ok(())
86 | }
87 |
88 | #[test]
89 | fn test_ident_substitution() -> Result<()> {
90 | let output = init_path(SCOPE, "test_ident_substitution");
91 |
92 | let hola = 7;
93 | let foo = 3;
94 | let bar = 4;
95 |
96 | let prefix = ts_string! {
97 | const val = #hola + #{foo + bar};
98 | const lemon = #"`egg salad sandwich ${val}`";
99 | const peas = #"`egg salad sandwich ${val} == #foo`";
100 | const soup = #"`egg salad sandwich ${val} == #{foo - bar} something something`";
101 | };
102 |
103 | export_types!(
104 | types: [],
105 | destinations: [(
106 | output.ts_path(),
107 | prefix: prefix,
108 | emitters: [
109 | TSFormat(
110 | tab_size: 2,
111 | line_width: 80,
112 | ),
113 | ],
114 | )]
115 | )?;
116 | Ok(())
117 | }
118 |
--------------------------------------------------------------------------------
/type_reflect/tests/test_untagged_enum.rs:
--------------------------------------------------------------------------------
1 | mod common;
2 |
3 | use anyhow::Result;
4 | use common::*;
5 |
6 | use serde::{Deserialize, Serialize};
7 | use type_reflect::*;
8 |
9 | #[derive(Serialize, Deserialize, Reflect)]
10 | pub struct Rectangle {
11 | width: f32,
12 | height: f32,
13 | }
14 |
15 | #[derive(Serialize, Deserialize, Reflect)]
16 | #[serde(rename_all = "camelCase")]
17 | pub enum Shape {
18 | Circle { radius: f32 },
19 | Square { side: f32 },
20 | Rectangle(Rectangle),
21 | Scale(f32),
22 | ScaledRectangle(Rectangle, f32),
23 | Null,
24 | }
25 |
26 | pub const SCOPE: &'static str = "test_untagged_enum";
27 |
28 | #[test]
29 | fn test_validation() -> Result<()> {
30 | let output = init_path(SCOPE, "test_validation");
31 |
32 | let value = Shape::Circle { radius: 5.0 };
33 | let json = serde_json::to_string_pretty(&value)?;
34 | println!("{json}");
35 |
36 | let value = Shape::Rectangle(Rectangle {
37 | width: 5.0,
38 | height: 5.0,
39 | });
40 | let json = serde_json::to_string_pretty(&value)?;
41 | println!("{json}");
42 |
43 | let value = Shape::ScaledRectangle(
44 | Rectangle {
45 | width: 5.0,
46 | height: 5.0,
47 | },
48 | 2.0,
49 | );
50 | let json = serde_json::to_string_pretty(&value)?;
51 | println!("{json}");
52 |
53 | let value = Shape::Scale(2.0);
54 | let json = serde_json::to_string_pretty(&value)?;
55 | println!("{json}");
56 |
57 | let value = Shape::Null;
58 | let json = serde_json::to_string_pretty(&value)?;
59 | println!("{json}");
60 |
61 | export_types!(
62 | types: [Rectangle, Shape],
63 | destinations: [(
64 | output.ts_path(),
65 | emitters: [
66 | TypeScript(),
67 | TSValidation(),
68 | TSFormat(
69 | tab_size: 2,
70 | line_width: 60,
71 | ),
72 | ],
73 | )]
74 | )?;
75 |
76 | output.write_jest(
77 | "Shape, Rectangle",
78 | ts_string! {
79 | describe("ADT Validation", ()=>{
80 | it("Validates a Null variant:", ()=>{
81 | expect(() => {
82 | Shape.validate("null")
83 | }).not.toThrow();
84 | });
85 | it("Validates a Circle variant:", ()=>{
86 | expect(() => {
87 | Shape.validate({
88 | circle: {
89 | radius: 1.7
90 | }
91 | })
92 | }).not.toThrow();
93 | });
94 | it("Validates a Rectangle variant:", ()=>{
95 | expect(() => {
96 | Shape.validate({
97 | rectangle: {
98 | width: 1,
99 | height: 2
100 | }
101 | })
102 | }).not.toThrow();
103 | });
104 | it("Validates a ScaledRectangle variant:", ()=>{
105 | expect(() => {
106 | Shape.validate({
107 |
108 | scaledRectangle: [
109 | {
110 | width: 1,
111 | height: 2
112 | },
113 | 0.5
114 | ]
115 | })
116 | }).not.toThrow();
117 | });
118 | it("Doesn't Validate an incorrect ScaledRectangle variant:", ()=>{
119 | expect(() => {
120 | Shape.validate({
121 | circle: [
122 | {
123 | width: 1,
124 | height: 2
125 | },
126 | 0.5
127 | ]
128 | })
129 | }).toThrow();
130 | });
131 |
132 | });
133 | }
134 | .as_str(),
135 | )?;
136 |
137 | output.run_ts().unwrap();
138 |
139 | Ok(())
140 | }
141 |
--------------------------------------------------------------------------------
/type_reflect_core/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "type_reflect_core"
3 | version = "0.5.0"
4 | authors = ["Spencer Kohan "]
5 | edition = "2021"
6 | description = "Utility functions for type_reflect"
7 | license = "Apache-2.0"
8 |
9 | repository = "https://github.com/spencerkohan/type_reflect/tree/main/type_reflect_core"
10 | documentation = "https://docs.rs/type_reflect_core"
11 |
12 | [dependencies]
13 | proc-macro2 = "1.0.69"
14 | quote = "1"
15 | syn = { version = "1", features = ["full", "extra-traits"] }
16 | Inflector = { version = "0.11", default-features = false }
17 |
--------------------------------------------------------------------------------
/type_reflect_core/README.md:
--------------------------------------------------------------------------------
1 | # Type Reflect Core
2 |
3 | [](type_reflect_core) [](https://crates.io/crates/type_reflect_core) [](https://docs.rs/type_reflect_core)
4 |
5 | A crate for shared components used by both `type_reflect` and `type_reflect_macros`.
6 |
7 | *This crate is part of a larger workspace, see the [monorepo README](https://github.com/spencerkohan/type_reflect) for more details*
8 |
--------------------------------------------------------------------------------
/type_reflect_core/src/inflection.rs:
--------------------------------------------------------------------------------
1 | use crate::syn_err;
2 | pub trait Inflectable {
3 | fn inflect(&self, inflection: Inflection) -> String;
4 | }
5 |
6 | impl Inflectable for &str {
7 | fn inflect(&self, inflection: Inflection) -> String {
8 | inflection.apply(self)
9 | }
10 | }
11 |
12 | impl Inflectable for String {
13 | fn inflect(&self, inflection: Inflection) -> String {
14 | inflection.apply(self.as_str())
15 | }
16 | }
17 |
18 | #[derive(Copy, Clone, Debug)]
19 | pub enum Inflection {
20 | Lower,
21 | Upper,
22 | Camel,
23 | Snake,
24 | Pascal,
25 | ScreamingSnake,
26 | Kebab,
27 | None,
28 | }
29 |
30 | impl Default for Inflection {
31 | fn default() -> Self {
32 | Self::None
33 | }
34 | }
35 |
36 | impl Inflection {
37 | pub fn apply(self, string: &str) -> String {
38 | use inflector::Inflector;
39 |
40 | match self {
41 | Inflection::Lower => string.to_lowercase(),
42 | Inflection::Upper => string.to_uppercase(),
43 | Inflection::Camel => string.to_camel_case(),
44 | Inflection::Snake => string.to_snake_case(),
45 | Inflection::Pascal => string.to_pascal_case(),
46 | Inflection::ScreamingSnake => string.to_screaming_snake_case(),
47 | Inflection::Kebab => string.to_kebab_case(),
48 | Inflection::None => string.to_string(),
49 | }
50 | }
51 | }
52 |
53 | impl TryFrom for Inflection {
54 | type Error = syn::Error;
55 |
56 | fn try_from(value: String) -> syn::Result {
57 | Ok(
58 | match &*value.to_lowercase().replace("_", "").replace("-", "") {
59 | "lowercase" => Self::Lower,
60 | "uppercase" => Self::Upper,
61 | "camelcase" => Self::Camel,
62 | "snakecase" => Self::Snake,
63 | "pascalcase" => Self::Pascal,
64 | "screamingsnakecase" => Self::ScreamingSnake,
65 | "kebabcase" => Self::Kebab,
66 | _ => syn_err!("invalid inflection: '{}'", value),
67 | },
68 | )
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/type_reflect_core/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod type_description;
2 | pub use type_description::*;
3 | pub mod inflection;
4 | pub use inflection::*;
5 |
6 | #[macro_export]
7 | macro_rules! syn_err {
8 | ($l:literal $(, $a:expr)*) => {
9 | syn_err!(proc_macro2::Span::call_site(); $l $(, $a)*)
10 | };
11 | ($s:expr; $l:literal $(, $a:expr)*) => {
12 | return Err(syn::Error::new($s, format!($l $(, $a)*)))
13 | };
14 | }
15 |
16 | #[macro_export]
17 | #[allow(unreachable_code)]
18 | macro_rules! impl_parse {
19 | ($i:ident ($input:ident, $out:ident) { $($k:pat => $e:expr),* $(,)? }) => {
20 | impl std::convert::TryFrom<&syn::Attribute> for $i {
21 | type Error = syn::Error;
22 | fn try_from(attr: &syn::Attribute) -> syn::Result { attr.parse_args() }
23 | }
24 |
25 | impl syn::parse::Parse for $i {
26 | fn parse($input: syn::parse::ParseStream) -> syn::Result {
27 | let mut $out = $i::default();
28 | loop {
29 | let key: Ident = $input.call(syn::ext::IdentExt::parse_any)?;
30 |
31 | match &*key.to_string() {
32 | $($k => $e,)*
33 | _ => syn_err!($input.span(); "unexpected attribute")
34 | };
35 |
36 | #[allow(unreachable_code)]
37 | match $input.is_empty() {
38 | true => break,
39 | false => {
40 | $input.parse::()?;
41 | }
42 | }
43 | }
44 |
45 | Ok($out)
46 | }
47 | }
48 | };
49 | }
50 |
--------------------------------------------------------------------------------
/type_reflect_core/src/type_description.rs:
--------------------------------------------------------------------------------
1 | use crate::{Inflectable, Inflection};
2 |
3 | #[derive(Clone, Debug)]
4 | pub struct NamedType {
5 | pub name: String,
6 | pub generic_args: Vec>,
7 | }
8 |
9 | #[derive(Clone, Debug)]
10 | pub enum TransparentTypeCase {
11 | Box,
12 | Rc,
13 | Arc,
14 | }
15 |
16 | #[derive(Clone, Debug)]
17 | pub struct TransparentType {
18 | pub case: TransparentTypeCase,
19 | pub type_: Box,
20 | }
21 |
22 | #[derive(Clone, Debug)]
23 | pub enum Type {
24 | Named(NamedType),
25 | String,
26 | Int,
27 | UnsignedInt,
28 | Float,
29 | Boolean,
30 | Transparent(TransparentType),
31 | Option(Box),
32 | Array(Box),
33 | Map { key: Box, value: Box },
34 | }
35 |
36 | #[derive(Clone, Debug)]
37 | pub struct NamedField {
38 | pub name: String,
39 | pub type_: Type,
40 | }
41 |
42 | #[derive(Clone, Debug)]
43 | pub struct EnumCase {
44 | pub name: String,
45 | pub type_: TypeFieldsDefinition,
46 | pub inflection: Inflection,
47 | }
48 |
49 | impl EnumCase {
50 | pub fn name_with_inflection(&self) -> String {
51 | self.name.inflect(self.inflection)
52 | }
53 | }
54 |
55 | /**
56 | The TypeFieldDefinition represents the set of fields for a type
57 |
58 | This is used both in the context of a struct definition, and for enum variants
59 | */
60 | #[derive(Clone, Debug)]
61 | pub enum TypeFieldsDefinition {
62 | /**
63 | The Unit field definition describes a type which does not contain data
64 | */
65 | Unit,
66 | /**
67 | The Tuple field definition describes a type which contains anonymous fields, identified by index
68 | */
69 | Tuple(Vec),
70 | /**
71 | The Named field definition describes a type which contains named fields, identified by name
72 | */
73 | Named(Vec),
74 | }
75 |
76 | #[derive(Clone, Debug)]
77 | pub enum EnumType {
78 | Simple,
79 | Complex {
80 | case_key: String,
81 | content_key: Option,
82 | },
83 | Untagged,
84 | }
85 |
86 | // #[derive(Clone, Debug)]
87 | // pub struct TypeSlot {
88 | // pub optional: bool,
89 | // pub type_: Type,
90 | // }
91 |
--------------------------------------------------------------------------------
/type_reflect_macros/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "type_reflect_macros"
3 | version = "0.5.1"
4 | authors = ["Spencer Kohan "]
5 | edition = "2021"
6 | description = "derive macro for type_reflect"
7 | license = "MIT"
8 |
9 | repository = "https://github.com/spencerkohan/type_reflect/tree/main/type_reflect_macros"
10 | documentation = "https://docs.rs/type_reflect_macros"
11 |
12 | [lib]
13 | proc-macro = true
14 |
15 | [dependencies]
16 | type_reflect_core = "0.5.0"
17 | proc-macro2 = "1.0.69"
18 | quote = "1"
19 | syn = { version = "1", features = ["full", "extra-traits"] }
20 | Inflector = { version = "0.11", default-features = false }
21 |
--------------------------------------------------------------------------------
/type_reflect_macros/README.md:
--------------------------------------------------------------------------------
1 | # Type Reflect Macros
2 |
3 | [](type_reflect_macros) [](https://crates.io/crates/type_reflect_macros) [](https://docs.rs/type_reflect_macros)
4 |
5 | Procedural macro implementations for `type_reflect`. This crate is for internal use, and the macros are re-exported by the `type_reflect` crate.
6 |
7 | *This crate is part of a larger workspace, see the [monorepo README](https://github.com/spencerkohan/type_reflect) for more details*
8 |
--------------------------------------------------------------------------------
/type_reflect_macros/src/attribute_utils.rs:
--------------------------------------------------------------------------------
1 | use syn::parse::{Parse, ParseStream};
2 | use syn::{Attribute, Ident, Lit, Result, Token};
3 | pub use type_reflect_core::inflection::*;
4 | use type_reflect_core::{impl_parse, syn_err};
5 |
6 | #[derive(Default, Clone, Debug)]
7 | pub struct RenameAllAttr {
8 | pub rename_all: Inflection,
9 | }
10 |
11 | impl RenameAllAttr {
12 | pub fn from_attrs(attrs: &[Attribute]) -> Result {
13 | let mut result = Self::default();
14 | parse_attrs(attrs)?.for_each(|a| result.merge(a));
15 | parse_serde_attrs::(attrs).for_each(|a| result.merge(a));
16 | Ok(result)
17 | }
18 |
19 | fn merge(&mut self, RenameAllAttr { rename_all }: RenameAllAttr) {
20 | self.rename_all = rename_all;
21 | }
22 | }
23 |
24 | impl_parse! {
25 | RenameAllAttr(input, out) {
26 | "rename_all" => out.rename_all = parse_assign_inflection(input)?,
27 | }
28 | }
29 |
30 | /// Parse all `#[ts(..)]` attributes from the given slice.
31 | pub fn parse_attrs<'a, A>(attrs: &'a [Attribute]) -> Result>
32 | where
33 | A: TryFrom<&'a Attribute, Error = syn::Error>,
34 | {
35 | Ok(attrs
36 | .iter()
37 | .filter(|a| a.path.is_ident("ts"))
38 | .map(A::try_from)
39 | .collect::>>()?
40 | .into_iter())
41 | }
42 |
43 | /// Parse all `#[serde(..)]` attributes from the given slice.
44 | // #[cfg(feature = "serde-compat")]
45 | #[allow(unused)]
46 | pub fn parse_serde_attrs<'a, A: TryFrom<&'a Attribute, Error = syn::Error>>(
47 | attrs: &'a [Attribute],
48 | ) -> impl Iterator- {
49 | attrs
50 | .iter()
51 | .filter(|a| a.path.is_ident("serde"))
52 | .flat_map(|attr| match A::try_from(attr) {
53 | Ok(attr) => Some(attr),
54 | Err(_) => {
55 | use quote::ToTokens;
56 | // warning::print_warning(
57 | // "failed to parse serde attribute",
58 | // format!("{}", attr.to_token_stream()),
59 | // "ts-rs failed to parse this attribute. It will be ignored.",
60 | // )
61 | // .unwrap();
62 | None
63 | }
64 | })
65 | .collect::>()
66 | .into_iter()
67 | }
68 |
69 | pub fn parse_assign_str(input: ParseStream) -> Result {
70 | input.parse::()?;
71 | match Lit::parse(input)? {
72 | Lit::Str(string) => Ok(string.value()),
73 | other => Err(syn::Error::new(other.span(), "expected string")),
74 | }
75 | }
76 |
77 | pub fn parse_assign_inflection(input: ParseStream) -> Result {
78 | match parse_assign_str(input) {
79 | Ok(str) => Inflection::try_from(str),
80 | Err(_) => Ok(Inflection::None),
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/type_reflect_macros/src/export_types_impl/destination.rs:
--------------------------------------------------------------------------------
1 | use proc_macro2::*;
2 | use quote::*;
3 | use syn::parse::{Parse, ParseStream};
4 | use syn::token::Paren;
5 | use syn::*;
6 |
7 | use super::{peak_arg_name, DestinationArg, NamedArg};
8 |
9 | #[derive(Debug, Clone)]
10 | pub enum Destination {
11 | Named(NamedDestination),
12 | Unnamed(UnnamedDestination),
13 | }
14 |
15 | impl Parse for Destination {
16 | fn parse(input: ParseStream) -> Result {
17 | if input.peek(syn::token::Paren) {
18 | let dest = input.parse()?;
19 | return Ok(Destination::Unnamed(dest));
20 | }
21 | let dest = input.parse()?;
22 | return Ok(Destination::Named(dest));
23 | }
24 | }
25 |
26 | #[derive(Debug, Clone)]
27 | pub struct NamedDestination {
28 | pub export_type: Expr,
29 | pub destinations: Vec,
30 | pub named_args: Vec,
31 | pub prefix: Option,
32 | pub postfix: Option,
33 | }
34 |
35 | impl Parse for NamedDestination {
36 | fn parse(input: ParseStream) -> Result {
37 | let mut export_type_tokens: TokenStream = quote! {};
38 |
39 | while !input.peek(syn::token::Paren) && !input.is_empty() {
40 | let next: TokenTree = input.parse()?;
41 | export_type_tokens.append(next);
42 | }
43 |
44 | let export_type: Expr = syn::parse2(export_type_tokens)?;
45 |
46 | let content;
47 | let _parens: Paren = parenthesized!(content in input);
48 |
49 | let mut args: Vec = vec![];
50 |
51 | while !content.is_empty() {
52 | let arg: DestinationArg = content.parse()?;
53 | args.push(arg);
54 | if content.peek(Token![,]) {
55 | let _comma: Token![,] = content.parse()?;
56 | }
57 | }
58 |
59 | let mut named_args: Vec = vec![];
60 |
61 | let destinations: Vec = args
62 | .into_iter()
63 | .filter_map(|arg| match arg {
64 | DestinationArg::Dest(expr) => Some(expr),
65 | DestinationArg::Named(arg) => {
66 | named_args.push(arg);
67 | None
68 | }
69 | })
70 | .collect();
71 |
72 | let mut prefix: Option = None;
73 | let mut postfix: Option = None;
74 | let named_args = named_args
75 | .into_iter()
76 | .filter(|arg| {
77 | match arg.name().as_str() {
78 | "prefix" => {
79 | prefix = Some(arg.expr.clone());
80 | return false;
81 | }
82 | "postfix" => {
83 | postfix = Some(arg.expr.clone());
84 | return false;
85 | }
86 | _ => {}
87 | };
88 | true
89 | })
90 | .collect();
91 |
92 | Ok(Self {
93 | export_type,
94 | destinations,
95 | named_args,
96 | prefix,
97 | postfix,
98 | })
99 | }
100 | }
101 |
102 | #[derive(Debug, Clone)]
103 | pub struct EmitterDecl {
104 | pub type_name: Expr,
105 | pub args: Vec,
106 | }
107 |
108 | impl Parse for EmitterDecl {
109 | fn parse(input: ParseStream) -> Result {
110 | let mut export_type_tokens: TokenStream = quote! {};
111 |
112 | while !input.peek(syn::token::Paren) && !input.is_empty() {
113 | let next: TokenTree = input.parse()?;
114 | export_type_tokens.append(next);
115 | }
116 |
117 | let type_name: Expr = syn::parse2(export_type_tokens)?;
118 |
119 | let content;
120 | let _parens: Paren = parenthesized!(content in input);
121 |
122 | let mut args: Vec = vec![];
123 |
124 | while !content.is_empty() {
125 | let arg: NamedArg = content.parse()?;
126 | args.push(arg);
127 | if content.peek(Token![,]) {
128 | let _comma: Token![,] = content.parse()?;
129 | }
130 | }
131 |
132 | Ok(Self { type_name, args })
133 | }
134 | }
135 |
136 | #[derive(Debug, Clone)]
137 | pub struct EmitterDeclList {
138 | pub emitters: Vec,
139 | }
140 |
141 | impl Parse for EmitterDeclList {
142 | fn parse(input: ParseStream) -> Result {
143 | let content;
144 | let _parens: token::Bracket = bracketed!(content in input);
145 | let mut emitters: Vec = vec![];
146 |
147 | while !content.is_empty() {
148 | let emitter: EmitterDecl = content.parse()?;
149 | emitters.push(emitter);
150 | if content.peek(Token![,]) {
151 | let _comma: Token![,] = content.parse()?;
152 | }
153 | }
154 |
155 | Ok(Self { emitters })
156 | }
157 | }
158 |
159 | #[derive(Debug, Clone)]
160 | pub struct UnnamedDestination {
161 | pub destinations: Vec,
162 | pub prefix: Option,
163 | pub postfix: Option,
164 | pub emitters: Vec,
165 | }
166 |
167 | impl Parse for UnnamedDestination {
168 | fn parse(input: ParseStream) -> Result {
169 | let content;
170 | let _parens: Paren = parenthesized!(content in input);
171 |
172 | // let mut args: Vec = vec![];
173 |
174 | let mut destinations: Vec = vec![];
175 | let mut prefix: Option = None;
176 | let mut postfix: Option = None;
177 | let mut emitters: EmitterDeclList = EmitterDeclList { emitters: vec![] };
178 |
179 | while !content.is_empty() {
180 | match peak_arg_name(&&content) {
181 | Some(name) => match name.to_string().as_str() {
182 | "prefix" => {
183 | let _: Ident = content.parse()?;
184 | let _: Token![:] = content.parse()?;
185 | prefix = Some(content.parse()?);
186 | }
187 | "postfix" => {
188 | let _: Ident = content.parse()?;
189 | let _: Token![:] = content.parse()?;
190 | postfix = Some(content.parse()?);
191 | }
192 | "emitters" => {
193 | let _: Ident = content.parse()?;
194 | let _: Token![:] = content.parse()?;
195 | emitters = content.parse()?;
196 | }
197 | _other => {
198 | // TODO: this should produce an error
199 | }
200 | },
201 | None => {
202 | let dest: Expr = content.parse()?;
203 | destinations.push(dest);
204 | }
205 | }
206 | if content.peek(Token![,]) {
207 | let _comma: Token![,] = content.parse()?;
208 | }
209 | }
210 |
211 | Ok(Self {
212 | destinations,
213 | prefix,
214 | postfix,
215 | emitters: emitters.emitters,
216 | })
217 | }
218 | }
219 |
220 | pub fn emit_destination(dest: &Destination, types: &Vec<&Ident>) -> TokenStream {
221 | match dest {
222 | Destination::Named(dest) => emit_named_destination(dest, types),
223 | Destination::Unnamed(dest) => emit_unnamed_destination(dest, types),
224 | }
225 | }
226 |
227 | pub fn emit_named_destination(dest: &NamedDestination, types: &Vec<&Ident>) -> TokenStream {
228 | let emitter = &dest.export_type;
229 |
230 | let prefix = match &dest.prefix {
231 | Some(expr) => {
232 | quote! { #expr }
233 | }
234 | None => quote! { "" },
235 | };
236 |
237 | let postfix = &dest.postfix;
238 |
239 | let emitter_args = &dest.named_args;
240 | let emitter_args = quote! { #(#emitter_args,)* };
241 |
242 | let mut result = quote! {};
243 | for dest in &dest.destinations {
244 | result.extend(quote! {
245 | let mut file = type_reflect::init_destination_file(#dest, #prefix)?;
246 | let mut emitter = #emitter {
247 | #emitter_args
248 | ..Default::default()
249 | };
250 | file.write_all(emitter.prefix().as_bytes())?;
251 | });
252 | for type_ in types {
253 | result.extend(quote! {
254 | file.write_all(emitter.emit::<#type_>().as_bytes())?;
255 | });
256 | }
257 | result.extend(quote! {
258 | emitter.finalize(#dest)?;
259 | });
260 | match postfix {
261 | Some(expr) => result.extend(quote! {
262 | type_reflect::write_postfix(#dest, #expr)?;
263 | }),
264 | None => {}
265 | };
266 | }
267 | result
268 | }
269 |
270 | pub fn emit_single_emitter(emitter: &EmitterDecl, types: &Vec<&Ident>, dest: &Expr) -> TokenStream {
271 | let emitter_name = &emitter.type_name;
272 |
273 | let emitter_args = &emitter.args;
274 | let emitter_args = quote! { #(#emitter_args,)* };
275 |
276 | let mut result = quote! {};
277 | result.extend(quote! {
278 | let mut emitter = #emitter_name {
279 | #emitter_args
280 | ..Default::default()
281 | };
282 | file.write_all(emitter.prefix().as_bytes())?;
283 | });
284 | for type_ in types {
285 | result.extend(quote! {
286 | file.write_all(emitter.emit::<#type_>().as_bytes())?;
287 | });
288 | }
289 | result.extend(quote! {
290 | emitter.finalize(#dest)?;
291 | });
292 |
293 | result
294 | }
295 |
296 | pub fn emit_unnamed_destination(dest: &UnnamedDestination, types: &Vec<&Ident>) -> TokenStream {
297 | let prefix = match &dest.prefix {
298 | Some(expr) => {
299 | quote! { #expr }
300 | }
301 | None => quote! { "" },
302 | };
303 |
304 | let postfix = &dest.postfix;
305 |
306 | let emitters = &dest.emitters;
307 |
308 | let mut result = quote! {};
309 | for dest in &dest.destinations {
310 | result.extend(quote! {
311 | let mut file = type_reflect::init_destination_file(#dest, #prefix)?;
312 | });
313 | for emitter in emitters {
314 | result.extend(quote! {
315 | let mut file = std::fs::OpenOptions::new()
316 | .write(true)
317 | .append(true)
318 | .open(#dest)?;
319 | });
320 | result.extend(emit_single_emitter(emitter, types, dest));
321 | }
322 | match postfix {
323 | Some(expr) => result.extend(quote! {
324 | type_reflect::write_postfix(#dest, #expr)?;
325 | }),
326 | None => {}
327 | };
328 | }
329 | result
330 | }
331 |
--------------------------------------------------------------------------------
/type_reflect_macros/src/export_types_impl/mod.rs:
--------------------------------------------------------------------------------
1 | use proc_macro2::*;
2 | use quote::*;
3 | use syn::parse::{Parse, ParseStream};
4 | use syn::punctuated::*;
5 | use syn::token::Bracket;
6 | use syn::*;
7 | mod destination;
8 | use destination::*;
9 |
10 | #[derive(Debug, Clone)]
11 | struct ItemsList {
12 | idents: Punctuated,
13 | }
14 |
15 | impl ItemsList {
16 | fn args(&self) -> Vec<&Ident> {
17 | (&self.idents).into_iter().collect()
18 | }
19 | }
20 |
21 | impl Parse for ItemsList {
22 | fn parse(input: ParseStream) -> Result {
23 | let ident: Ident = input.parse()?;
24 | if ident.to_string().as_str() != "types" {
25 | return Err(syn::Error::new(
26 | ident.span(),
27 | r#"Expected argument name: "types""#,
28 | ));
29 | }
30 | let _colon_token: Token![:] = input.parse()?;
31 | let content;
32 | let _brackets: Bracket = bracketed!(content in input);
33 | let idents = content.parse_terminated(Ident::parse)?;
34 | Ok(Self { idents })
35 | }
36 | }
37 |
38 | #[derive(Debug, Clone)]
39 | struct DestinationList {
40 | destinations: Vec,
41 | }
42 |
43 | impl Parse for DestinationList {
44 | fn parse(input: ParseStream) -> Result {
45 | let ident: Ident = input.parse()?;
46 | if ident.to_string().as_str() != "destinations" {
47 | return Err(syn::Error::new(
48 | ident.span(),
49 | r#"Expected argument name: "destinations""#,
50 | ));
51 | }
52 |
53 | let _colon_token: Token![:] = input.parse()?;
54 | let content;
55 | let _brackets: Bracket = bracketed!(content in input);
56 | let destinations: Punctuated =
57 | match content.parse_terminated(Destination::parse) {
58 | Ok(res) => res,
59 | Err(err) => {
60 | return Err(syn::Error::new(
61 | err.span(),
62 | format!("Error parsing destinations list: {}", err),
63 | ));
64 | }
65 | };
66 |
67 | let destinations: Vec = destinations.into_iter().map(|dest| dest).collect();
68 |
69 | Ok(Self { destinations })
70 | }
71 | }
72 |
73 | #[derive(Debug, Clone)]
74 | pub struct NamedArg {
75 | ident: Ident,
76 | expr: Expr,
77 | }
78 |
79 | impl NamedArg {
80 | pub fn name(&self) -> String {
81 | self.ident.to_string()
82 | }
83 | }
84 |
85 | impl ToTokens for NamedArg {
86 | fn to_tokens(&self, tokens: &mut TokenStream) {
87 | let ident = &self.ident;
88 | let expr = &self.expr;
89 | tokens.extend(quote! { #ident: #expr })
90 | }
91 | }
92 |
93 | impl Parse for NamedArg {
94 | fn parse(input: ParseStream) -> Result {
95 | let ident: Ident = input.parse()?;
96 |
97 | let _colon_token: Token![:] = input.parse()?;
98 | let expr: Expr = input.parse()?;
99 |
100 | Ok(Self { ident, expr })
101 | }
102 | }
103 |
104 | #[derive(Debug, Clone)]
105 | pub enum DestinationArg {
106 | Dest(Expr),
107 | Named(NamedArg),
108 | }
109 |
110 | pub fn peak_arg_name(input: &syn::parse::ParseStream) -> Option {
111 | let lookahead = input.lookahead1();
112 | if lookahead.peek(Ident) {
113 | let forked = input.fork();
114 | let ident: Ident = match forked.parse::() {
115 | Ok(ident) => ident,
116 | Err(err) => {
117 | eprintln!("Failed to get ident");
118 | panic!("{}", err);
119 | }
120 | };
121 | if forked.parse::().is_ok() && !forked.lookahead1().peek(Token![:]) {
122 | // !forked.lookahead1().peek(Ident) {
123 | // We are fairly certain it's a KeyValuePair now
124 | return Some(ident);
125 | }
126 | }
127 | None
128 | }
129 |
130 | impl Parse for DestinationArg {
131 | fn parse(input: syn::parse::ParseStream) -> Result {
132 | let lookahead = input.lookahead1();
133 |
134 | if lookahead.peek(Ident) {
135 | let forked = input.fork();
136 | let _ident: Ident = forked.parse()?;
137 | if forked.parse::().is_ok() && !forked.lookahead1().peek(Ident) {
138 | // We are fairly certain it's a KeyValuePair now
139 | let prefix = input.parse::()?;
140 | return Ok(DestinationArg::Named(prefix));
141 | }
142 | }
143 | let expr: Expr = input.parse()?;
144 | Ok(DestinationArg::Dest(expr))
145 | }
146 | }
147 |
148 | #[derive(Debug, Clone)]
149 | struct Input {
150 | items: ItemsList,
151 | destinations: DestinationList,
152 | }
153 |
154 | impl Parse for Input {
155 | fn parse(input: ParseStream) -> Result {
156 | let items = input.parse()?;
157 | let _comma_token: Token![,] = input.parse()?;
158 | let destinations = input.parse()?;
159 | Ok(Self {
160 | items,
161 | destinations,
162 | })
163 | }
164 | }
165 |
166 | pub fn export_types_impl(input: proc_macro::TokenStream) -> Result {
167 | // println!("EXPORT TYPES input: {:#?}", input);
168 | let input = syn::parse::(input)?;
169 | // println!("parse result: {:#?}", input);
170 |
171 | let types = input.items.args();
172 | let destinations = input.destinations.destinations;
173 |
174 | let mut result = quote! {};
175 | for dest in destinations {
176 | result.extend(emit_destination(&dest, &types))
177 | }
178 |
179 | let result = quote! {
180 | (|| -> Result<(), std::io::Error> {
181 | #result
182 | Ok(())
183 | })()
184 | };
185 |
186 | // println!("Emitting: {}", result);
187 | // Ok(input)
188 | Ok(result)
189 | }
190 |
--------------------------------------------------------------------------------
/type_reflect_macros/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(incomplete_features)]
2 | #![feature(specialization)]
3 | #![macro_use]
4 | // #![deny(unused)]
5 |
6 | use proc_macro2::TokenStream;
7 | use syn::{spanned::Spanned, Item, Result};
8 |
9 | mod type_def;
10 | use type_def::*;
11 | mod attribute_utils;
12 |
13 | mod export_types_impl;
14 | use export_types_impl::*;
15 | use type_reflect_core::syn_err;
16 |
17 | #[macro_use]
18 | mod utils;
19 |
20 | /// Derives [TS](./trait.TS.html) for a struct or enum.
21 | /// Please take a look at [TS](./trait.TS.html) for documentation.
22 | #[proc_macro_derive(Reflect, attributes(reflect))]
23 | pub fn reflect(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
24 | match entry(input) {
25 | Err(err) => err.to_compile_error(),
26 | Ok(result) => result,
27 | }
28 | .into()
29 | }
30 |
31 | fn entry(input: proc_macro::TokenStream) -> Result {
32 | let input = syn::parse::
- (input)?;
33 |
34 | // Access the attributes of the input item
35 |
36 | let (type_def, _ident, _generics) = match input {
37 | Item::Struct(s) => {
38 | // println!("Parsed Item::Struct: {:#?}", s);
39 | (TypeDef::struct_def(&s)?, s.ident, s.generics)
40 | }
41 | Item::Enum(e) => {
42 | // println!("Parsed Item::Enum: {:#?}", e);
43 | (TypeDef::enum_def(&e)?, e.ident, e.generics)
44 | }
45 | Item::Type(t) => (TypeDef::alias_def(&t)?, t.ident, t.generics),
46 | _ => {
47 | syn_err!(input.span(); "Item is not supported by the Reflect macro")
48 | }
49 | };
50 |
51 | // println!("Type Def Parsed: {:#?}", type_def);
52 | // println!("Type Def Emits: \n{}", type_def.emit());
53 |
54 | Ok(type_def.emit())
55 |
56 | // Ok(ts.into_impl(ident, generics))
57 | }
58 |
59 | #[proc_macro]
60 | pub fn export_types(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
61 | match export_types_impl(input) {
62 | Err(err) => err.to_compile_error(),
63 | Ok(result) => result,
64 | }
65 | .into()
66 | }
67 |
--------------------------------------------------------------------------------
/type_reflect_macros/src/type_def/enum_def.rs:
--------------------------------------------------------------------------------
1 | use crate::attribute_utils::*;
2 | use crate::type_def::InflectionTokenProvider;
3 | // use crate::utils::*;
4 | use type_reflect_core::EnumType;
5 | use type_reflect_core::Inflection;
6 |
7 | use super::{syn_type_utils::*, type_utils::TypeFieldsDefinitionBridge, RustTypeEmitter};
8 | use proc_macro2::{Ident, TokenStream};
9 | use quote::quote;
10 | use syn::{
11 | // parse::{Parse, ParseStream},
12 | Attribute,
13 | ItemEnum,
14 | Result,
15 | };
16 | use type_reflect_core::*;
17 |
18 | #[derive(Clone, Debug)]
19 | pub struct EnumDef {
20 | pub tokens: TokenStream,
21 | pub ident: Ident,
22 | pub enum_type: EnumType,
23 | pub inflection: Inflection,
24 | pub cases: Vec,
25 | }
26 |
27 | fn extract_cases(item: &ItemEnum) -> Result> {
28 | (&item.variants)
29 | .into_iter()
30 | .map(|case| {
31 | let name = format!("{}", case.ident);
32 | let inflection: Inflection = match RenameAllAttr::from_attrs(&case.attrs) {
33 | Err(e) => {
34 | eprintln!(
35 | "Error extracting inflection: {} from attributes: {:#?}",
36 | e, &case.attrs
37 | );
38 | Inflection::None
39 | }
40 | Ok(rename_all) => {
41 | // println!(
42 | // "Extracted inflection: {:?} from attributes: {:#?}",
43 | // rename_all, &case.attrs
44 | // );
45 | rename_all.rename_all
46 | }
47 | };
48 | Ok(EnumCase {
49 | name,
50 | type_: (&case.fields).to_fields()?,
51 | inflection,
52 | })
53 | })
54 | .collect()
55 | }
56 |
57 | impl EnumDef {
58 | pub fn new(item: &ItemEnum) -> Result {
59 | let attributes = EnumAttr::from_attrs(&item.attrs)?;
60 | let rename_attr = RenameAllAttr::from_attrs(&item.attrs)?;
61 |
62 | let cases = extract_cases(&item)?;
63 |
64 | let enum_type = match (&cases).into_iter().fold(false, |input, case| {
65 | input
66 | || if let TypeFieldsDefinition::Unit = case.type_ {
67 | false
68 | } else {
69 | true
70 | }
71 | }) {
72 | // false indicates it is not complex
73 | false => EnumType::Simple,
74 | // true indicates the type is complex
75 | true => match attributes.tag {
76 | Some(case_key) => {
77 | let content_key = attributes.content;
78 | EnumType::Complex {
79 | case_key,
80 | content_key,
81 | }
82 | }
83 | None => EnumType::Untagged,
84 | },
85 | };
86 |
87 | Ok(Self {
88 | tokens: quote! { #item },
89 | ident: item.ident.clone(),
90 | enum_type,
91 | inflection: rename_attr.rename_all,
92 | cases,
93 | })
94 | }
95 |
96 | pub fn emit_cases(&self) -> TokenStream {
97 | let cases: Vec = (&self.cases)
98 | .into_iter()
99 | .map(|case| {
100 | let name = &case.name;
101 | let type_ = case.type_.emit_def();
102 | let rename_all = &case.inflection.to_tokens();
103 | quote! {
104 | EnumCase {
105 | name: #name.to_string(),
106 | type_: #type_,
107 | inflection: #rename_all,
108 | }
109 | }
110 | })
111 | .collect();
112 | quote! {
113 | #(#cases),*
114 | }
115 | }
116 |
117 | pub fn emit(&self) -> TokenStream {
118 | let ident = &self.ident();
119 | let name_literal = format!("{}", ident);
120 | let cases = &self.emit_cases();
121 | let rust = format!("{}", self.tokens());
122 |
123 | let enum_type = match &self.enum_type {
124 | EnumType::Simple => quote! {EnumType::Simple},
125 | EnumType::Complex {
126 | case_key,
127 | content_key,
128 | } => match content_key {
129 | Some(content_key) => quote! {
130 | EnumType::Complex {
131 | case_key: #case_key.to_string(),
132 | content_key: Some(#content_key.to_string())
133 | }
134 | },
135 | None => quote! {
136 | EnumType::Complex { case_key: #case_key.to_string(), content_key: None }
137 | },
138 | },
139 | EnumType::Untagged => quote! { EnumType::Untagged },
140 | };
141 |
142 | let inflection = &self.inflection.to_tokens();
143 |
144 | quote! {
145 |
146 | impl Emittable for #ident {
147 | fn emit_with(emitter: &mut E) -> String {
148 | emitter.emit_enum::()
149 | }
150 | }
151 |
152 | impl EnumReflectionType for #ident {
153 | fn name() -> &'static str {
154 | #name_literal
155 | }
156 | fn inflection() -> Inflection {
157 | #inflection
158 | }
159 | fn enum_type() -> EnumType {
160 | #enum_type
161 | }
162 | fn cases() -> Vec {
163 | vec![
164 | #cases
165 | ]
166 | }
167 | fn rust() -> String {
168 | #rust.to_string()
169 | }
170 | }
171 |
172 | }
173 | }
174 | }
175 |
176 | impl RustTypeEmitter for EnumDef {
177 | fn ident(&self) -> &Ident {
178 | &self.ident
179 | }
180 | fn tokens(&self) -> &TokenStream {
181 | &self.tokens
182 | }
183 | }
184 |
185 | #[derive(Default, Clone, Debug)]
186 | pub struct EnumAttr {
187 | // pub rename_all: Option,
188 | // pub rename: Option,
189 | // pub export_to: Option,
190 | // pub export: bool,
191 | tag: Option,
192 | // untagged: bool,
193 | content: Option,
194 | }
195 |
196 | #[derive(Default)]
197 | pub struct SerdeEnumAttr(EnumAttr);
198 |
199 | impl EnumAttr {
200 | // pub fn tagged(&self) -> Result> {
201 | // match (false, &self.tag, &self.content) {
202 | // (false, None, None) => Ok(Tagged::Externally),
203 | // (false, Some(tag), None) => Ok(Tagged::Internally { tag }),
204 | // (false, Some(tag), Some(content)) => Ok(Tagged::Adjacently { tag, content }),
205 | // (true, None, None) => Ok(Tagged::Untagged),
206 | // (true, Some(_), None) => syn_err!("untagged cannot be used with tag"),
207 | // (true, _, Some(_)) => syn_err!("untagged cannot be used with content"),
208 | // (false, None, Some(_)) => syn_err!("content cannot be used without tag"),
209 | // }
210 | // }
211 |
212 | pub fn from_attrs(attrs: &[Attribute]) -> Result {
213 | let mut result = Self::default();
214 | parse_attrs(attrs)?.for_each(|a| result.merge(a));
215 | // #[cfg(feature = "serde-compat")]
216 | parse_serde_attrs::(attrs).for_each(|a| result.merge(a.0));
217 | Ok(result)
218 | }
219 |
220 | fn merge(
221 | &mut self,
222 | EnumAttr {
223 | // rename_all,
224 | // rename,
225 | tag,
226 | content,
227 | // untagged,
228 | // export_to,
229 | // export,
230 | }: EnumAttr,
231 | ) {
232 | // self.rename = self.rename.take().or(rename);
233 | // self.rename_all = self.rename_all.take().or(rename_all);
234 | self.tag = self.tag.take().or(tag);
235 | // self.untagged = self.untagged || untagged;
236 | self.content = self.content.take().or(content);
237 | // self.export = self.export || export;
238 | // self.export_to = self.export_to.take().or(export_to);
239 | }
240 | }
241 |
242 | impl_parse! {
243 | EnumAttr(_input, _out) {
244 | // "rename" => out.rename = Some(parse_assign_str(input)?),
245 | // "rename_all" => out.rename_all = Some(parse_assign_inflection(input)?),
246 | // "export_to" => out.export_to = Some(parse_assign_str(input)?),
247 | // "export" => out.export = true
248 | }
249 | }
250 |
251 | impl_parse! {
252 | SerdeEnumAttr(input, out) {
253 | // "rename" => out.0.rename = Some(parse_assign_str(input)?),
254 | // "rename_all" => out.0.rename_all = Some(parse_assign_inflection(input)?),
255 | "tag" => out.0.tag = Some(parse_assign_str(input)?),
256 | "content" => out.0.content = Some(parse_assign_str(input)?),
257 | // "untagged" => out.0.untagged = true
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/type_reflect_macros/src/type_def/mod.rs:
--------------------------------------------------------------------------------
1 | use proc_macro2::{Ident, TokenStream};
2 | use quote::quote;
3 | use syn::{ItemEnum, ItemStruct, ItemType, Result};
4 |
5 | mod enum_def;
6 | mod struct_def;
7 | mod type_alias_def;
8 | pub use type_alias_def::*;
9 |
10 | pub mod syn_type_utils;
11 |
12 | pub mod type_utils;
13 |
14 | pub use enum_def::*;
15 | pub use struct_def::StructDef;
16 | use type_reflect_core::Inflection;
17 |
18 | #[derive(Clone, Debug)]
19 | pub enum TypeDef {
20 | Struct(StructDef),
21 | Enum(EnumDef),
22 | Alias(TypeAliasDef),
23 | }
24 |
25 | impl TypeDef {
26 | pub fn struct_def(item: &ItemStruct) -> Result {
27 | Ok(TypeDef::Struct(StructDef::new(item)?))
28 | }
29 | pub fn alias_def(item: &ItemType) -> Result {
30 | Ok(TypeDef::Alias(TypeAliasDef::new(item)?))
31 | }
32 | pub fn enum_def(item: &ItemEnum) -> Result {
33 | // println!("ATTRIBUTES:");
34 | // for attr in &item.attrs {
35 | // println!(" {:?}", attr);
36 | // }
37 | Ok(TypeDef::Enum(EnumDef::new(item)?))
38 | }
39 |
40 | pub fn emit(&self) -> TokenStream {
41 | match self {
42 | TypeDef::Struct(s) => s.emit(),
43 | TypeDef::Enum(e) => e.emit(),
44 | TypeDef::Alias(t) => t.emit(),
45 | }
46 | }
47 | }
48 |
49 | impl RustTypeEmitter for TypeDef {
50 | fn ident(&self) -> &Ident {
51 | panic!("unimplemented")
52 | }
53 | fn tokens(&self) -> &TokenStream {
54 | panic!("unimplemented")
55 | }
56 | // fn emit_type_def_impl(&self) -> TokenStream {
57 | // match self {
58 | // TypeDef::Struct(s) => s.emit_type_def_impl(),
59 | // TypeDef::Enum(e) => e.emit_type_def_impl(),
60 | // TypeDef::Alias(_) => todo!(),
61 | // }
62 | // }
63 | }
64 |
65 | pub trait RustTypeEmitter {
66 | fn ident(&self) -> &Ident;
67 | fn tokens(&self) -> &TokenStream;
68 | // fn emit_type_def_impl(&self) -> TokenStream {
69 | // let ident = &self.ident();
70 | // let token_string = format!("{}", self.tokens());
71 | // quote! {
72 | // impl RustType for #ident {
73 | // fn emit_rust(&self) -> String {
74 | // #token_string.to_string()
75 | // }
76 | // }
77 | // }
78 | // }
79 | }
80 |
81 | pub trait InflectionTokenProvider {
82 | fn inflection(&self) -> &Inflection;
83 | fn to_tokens(&self) -> TokenStream {
84 | match &self.inflection() {
85 | Inflection::Lower => quote!(Inflection::Lower),
86 | Inflection::Upper => quote!(Inflection::Upper),
87 | Inflection::Camel => quote!(Inflection::Camel),
88 | Inflection::Snake => quote!(Inflection::Snake),
89 | Inflection::Pascal => quote!(Inflection::Pascal),
90 | Inflection::ScreamingSnake => quote!(Inflection::ScreamingSnake),
91 | Inflection::Kebab => quote!(Inflection::Kebab),
92 | Inflection::None => quote!(Inflection::None),
93 | }
94 | }
95 | }
96 |
97 | impl InflectionTokenProvider for Inflection {
98 | fn inflection(&self) -> &Inflection {
99 | self
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/type_reflect_macros/src/type_def/struct_def.rs:
--------------------------------------------------------------------------------
1 | use super::syn_type_utils::*;
2 | use super::type_utils::*;
3 | use super::InflectionTokenProvider;
4 | use super::RustTypeEmitter;
5 | use crate::attribute_utils::RenameAllAttr;
6 | use proc_macro2::{Ident, TokenStream};
7 | use quote::quote;
8 | use syn::{ItemStruct, Result};
9 | use type_reflect_core::Inflection;
10 | use type_reflect_core::TypeFieldsDefinition;
11 |
12 | #[derive(Clone, Debug)]
13 | pub struct StructDef {
14 | tokens: TokenStream,
15 | inflection: Inflection,
16 | ident: Ident,
17 | fields: TypeFieldsDefinition,
18 | }
19 |
20 | // fn extract_members(item: &ItemStruct) -> Result {
21 | // match &(item.fields) {
22 | // syn::Fields::Named(fields) => (&fields).to_named_fields(),
23 | // syn::Fields::Unnamed(fieldsUnnamed) => todo!(),
24 | // syn::Fields::Unit => todo!(),
25 | // }
26 | // }
27 |
28 | impl StructDef {
29 | pub fn new(item: &ItemStruct) -> Result {
30 | let rename_attr = RenameAllAttr::from_attrs(&item.attrs)?;
31 | Ok(Self {
32 | tokens: quote! { #item },
33 | inflection: rename_attr.rename_all,
34 | ident: item.ident.clone(),
35 | fields: (&item.fields).to_fields()?,
36 | })
37 | }
38 |
39 | pub fn emit_fields(&self) -> TokenStream {
40 | // let members: Vec