@typokit/transform-native 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,179 @@
1
+ use std::collections::HashMap;
2
+ use serde::{Deserialize, Serialize};
3
+ use crate::type_extractor::TypeMetadata;
4
+
5
+ /// Metadata passed to JS callback for a single type
6
+ #[derive(Debug, Clone, Serialize, Deserialize)]
7
+ pub struct TypeValidatorInput {
8
+ pub name: String,
9
+ pub properties: HashMap<String, PropertyInput>,
10
+ }
11
+
12
+ #[derive(Debug, Clone, Serialize, Deserialize)]
13
+ pub struct PropertyInput {
14
+ #[serde(rename = "type")]
15
+ pub type_str: String,
16
+ pub optional: bool,
17
+ }
18
+
19
+ /// Prepare type metadata for the Typia bridge JS callback.
20
+ ///
21
+ /// Converts internal TypeMetadata into a serializable format that can be
22
+ /// passed across the napi-rs boundary to @typokit/transform-typia.
23
+ pub fn prepare_validator_inputs(
24
+ types: &HashMap<String, TypeMetadata>,
25
+ ) -> Vec<TypeValidatorInput> {
26
+ let mut inputs: Vec<TypeValidatorInput> = Vec::new();
27
+
28
+ // Sort by name for deterministic output
29
+ let mut names: Vec<&String> = types.keys().collect();
30
+ names.sort();
31
+
32
+ for name in names {
33
+ let meta = &types[name];
34
+ let mut properties = HashMap::new();
35
+
36
+ for (prop_name, prop) in &meta.properties {
37
+ properties.insert(
38
+ prop_name.clone(),
39
+ PropertyInput {
40
+ type_str: prop.type_str.clone(),
41
+ optional: prop.optional,
42
+ },
43
+ );
44
+ }
45
+
46
+ inputs.push(TypeValidatorInput {
47
+ name: name.clone(),
48
+ properties,
49
+ });
50
+ }
51
+
52
+ inputs
53
+ }
54
+
55
+ /// Collect validator results from the JS callback into a file output map.
56
+ ///
57
+ /// Maps type names to their generated validator file paths and code,
58
+ /// suitable for writing to .typokit/validators/.
59
+ pub fn collect_validator_outputs(
60
+ results: &[(String, String)],
61
+ ) -> HashMap<String, String> {
62
+ let mut output = HashMap::new();
63
+ for (type_name, code) in results {
64
+ let file_path = format!(".typokit/validators/{}.ts", to_file_name(type_name));
65
+ output.insert(file_path, code.clone());
66
+ }
67
+ output
68
+ }
69
+
70
+ /// Convert PascalCase to kebab-case for file names
71
+ fn to_file_name(name: &str) -> String {
72
+ let mut result = String::new();
73
+ for (i, ch) in name.chars().enumerate() {
74
+ if ch.is_uppercase() && i > 0 {
75
+ result.push('-');
76
+ }
77
+ result.push(ch.to_ascii_lowercase());
78
+ }
79
+ result
80
+ }
81
+
82
+ #[cfg(test)]
83
+ mod tests {
84
+ use super::*;
85
+ use crate::type_extractor::PropertyMetadata;
86
+
87
+ fn make_type(name: &str, props: Vec<(&str, &str, bool)>) -> TypeMetadata {
88
+ let mut properties = HashMap::new();
89
+ for (pname, ptype, optional) in props {
90
+ properties.insert(
91
+ pname.to_string(),
92
+ PropertyMetadata {
93
+ type_str: ptype.to_string(),
94
+ optional,
95
+ jsdoc: None,
96
+ },
97
+ );
98
+ }
99
+ TypeMetadata {
100
+ name: name.to_string(),
101
+ properties,
102
+ jsdoc: None,
103
+ }
104
+ }
105
+
106
+ #[test]
107
+ fn test_prepare_validator_inputs() {
108
+ let mut types = HashMap::new();
109
+ types.insert("User".to_string(), make_type("User", vec![
110
+ ("id", "string", false),
111
+ ("name", "string", false),
112
+ ("age", "number", true),
113
+ ]));
114
+
115
+ let inputs = prepare_validator_inputs(&types);
116
+
117
+ assert_eq!(inputs.len(), 1);
118
+ assert_eq!(inputs[0].name, "User");
119
+ assert_eq!(inputs[0].properties.len(), 3);
120
+ assert_eq!(inputs[0].properties["id"].type_str, "string");
121
+ assert!(!inputs[0].properties["id"].optional);
122
+ assert!(inputs[0].properties["age"].optional);
123
+ }
124
+
125
+ #[test]
126
+ fn test_prepare_validator_inputs_multiple_types() {
127
+ let mut types = HashMap::new();
128
+ types.insert("User".to_string(), make_type("User", vec![
129
+ ("id", "string", false),
130
+ ]));
131
+ types.insert("Post".to_string(), make_type("Post", vec![
132
+ ("id", "string", false),
133
+ ("title", "string", false),
134
+ ]));
135
+
136
+ let inputs = prepare_validator_inputs(&types);
137
+
138
+ assert_eq!(inputs.len(), 2);
139
+ // Should be sorted alphabetically
140
+ assert_eq!(inputs[0].name, "Post");
141
+ assert_eq!(inputs[1].name, "User");
142
+ }
143
+
144
+ #[test]
145
+ fn test_collect_validator_outputs() {
146
+ let results = vec![
147
+ ("User".to_string(), "export function validateUser(input: unknown) { /* ... */ }".to_string()),
148
+ ("Post".to_string(), "export function validatePost(input: unknown) { /* ... */ }".to_string()),
149
+ ];
150
+
151
+ let output = collect_validator_outputs(&results);
152
+
153
+ assert_eq!(output.len(), 2);
154
+ assert!(output.contains_key(".typokit/validators/user.ts"));
155
+ assert!(output.contains_key(".typokit/validators/post.ts"));
156
+ assert!(output[".typokit/validators/user.ts"].contains("validateUser"));
157
+ }
158
+
159
+ #[test]
160
+ fn test_to_file_name() {
161
+ assert_eq!(to_file_name("User"), "user");
162
+ assert_eq!(to_file_name("BlogPost"), "blog-post");
163
+ assert_eq!(to_file_name("APIKey"), "a-p-i-key");
164
+ }
165
+
166
+ #[test]
167
+ fn test_prepare_empty_types() {
168
+ let types = HashMap::new();
169
+ let inputs = prepare_validator_inputs(&types);
170
+ assert!(inputs.is_empty());
171
+ }
172
+
173
+ #[test]
174
+ fn test_collect_empty_results() {
175
+ let results: Vec<(String, String)> = Vec::new();
176
+ let output = collect_validator_outputs(&results);
177
+ assert!(output.is_empty());
178
+ }
179
+ }