ron2_doc/
example.rs

1//! RON example generation from schemas.
2
3use ahash::HashMap;
4use ron2::schema::{Schema, TypeKind, Variant, VariantKind};
5
6use crate::discovery::DiscoveredSchema;
7
8/// Generate a skeleton RON example from a schema.
9pub fn generate_example(
10    schema: &Schema,
11    max_depth: usize,
12    schemas: &HashMap<&str, &Schema>,
13) -> String {
14    generate_type_example(&schema.kind, max_depth, schemas, 0, 0)
15}
16
17fn generate_type_example(
18    kind: &TypeKind,
19    max_depth: usize,
20    schemas: &HashMap<&str, &Schema>,
21    current_depth: usize,
22    indent_level: usize,
23) -> String {
24    let indent = "    ".repeat(indent_level);
25    let inner_indent = "    ".repeat(indent_level + 1);
26
27    match kind {
28        // Primitives with placeholder values
29        TypeKind::Bool => "true".to_string(),
30        TypeKind::String => "\"...\"".to_string(),
31        TypeKind::Char => "'?'".to_string(),
32        TypeKind::I8 | TypeKind::I16 | TypeKind::I32 | TypeKind::I64 | TypeKind::I128 => {
33            "0".to_string()
34        }
35        TypeKind::U8 | TypeKind::U16 | TypeKind::U32 | TypeKind::U64 | TypeKind::U128 => {
36            "0".to_string()
37        }
38        TypeKind::F32 | TypeKind::F64 => "0.0".to_string(),
39        TypeKind::Unit => "()".to_string(),
40
41        // Compound types
42        TypeKind::Option(inner) => {
43            let inner_example =
44                generate_type_example(inner, max_depth, schemas, current_depth, indent_level);
45            format!("Some({})", inner_example)
46        }
47        TypeKind::List(inner) => {
48            let inner_example =
49                generate_type_example(inner, max_depth, schemas, current_depth, indent_level);
50            format!("[{}]", inner_example)
51        }
52        TypeKind::Map { key, value } => {
53            let key_example =
54                generate_type_example(key, max_depth, schemas, current_depth, indent_level);
55            let value_example =
56                generate_type_example(value, max_depth, schemas, current_depth, indent_level);
57            format!("{{ {}: {} }}", key_example, value_example)
58        }
59        TypeKind::Tuple(types) => {
60            let items: Vec<_> = types
61                .iter()
62                .map(|t| generate_type_example(t, max_depth, schemas, current_depth, indent_level))
63                .collect();
64            format!("({})", items.join(", "))
65        }
66
67        // Struct: show all required fields
68        TypeKind::Struct { fields } => {
69            let required_fields: Vec<_> = fields.iter().filter(|f| !f.optional).collect();
70
71            if required_fields.is_empty() {
72                return "()".to_string();
73            }
74
75            let field_examples: Vec<_> = required_fields
76                .iter()
77                .map(|f| {
78                    let value = generate_type_example(
79                        &f.ty,
80                        max_depth,
81                        schemas,
82                        current_depth + 1,
83                        indent_level + 1,
84                    );
85                    format!("{}{}: {}", inner_indent, f.name, value)
86                })
87                .collect();
88
89            format!("(\n{},\n{})", field_examples.join(",\n"), indent)
90        }
91
92        // Enum: show first variant
93        TypeKind::Enum { variants } => {
94            if let Some(variant) = variants.first() {
95                generate_variant_example(variant, max_depth, schemas, current_depth, indent_level)
96            } else {
97                "/* no variants */".to_string()
98            }
99        }
100
101        // TypeRef: resolve or use placeholder
102        TypeKind::TypeRef(path) => {
103            if current_depth < max_depth
104                && let Some(resolved) = schemas.get(path.as_str()) {
105                    return generate_type_example(
106                        &resolved.kind,
107                        max_depth,
108                        schemas,
109                        current_depth + 1,
110                        indent_level,
111                    );
112                }
113            // Use short name as placeholder
114            let short_name = path.split("::").last().unwrap_or(path);
115            format!("/* {} */", short_name)
116        }
117    }
118}
119
120fn generate_variant_example(
121    variant: &Variant,
122    max_depth: usize,
123    schemas: &HashMap<&str, &Schema>,
124    current_depth: usize,
125    indent_level: usize,
126) -> String {
127    match &variant.kind {
128        VariantKind::Unit => variant.name.clone(),
129        VariantKind::Tuple(types) => {
130            let items: Vec<_> = types
131                .iter()
132                .map(|t| generate_type_example(t, max_depth, schemas, current_depth, indent_level))
133                .collect();
134            format!("{}({})", variant.name, items.join(", "))
135        }
136        VariantKind::Struct(fields) => {
137            let inner_indent = "    ".repeat(indent_level + 1);
138            let field_examples: Vec<_> = fields
139                .iter()
140                .filter(|f| !f.optional)
141                .map(|f| {
142                    let value = generate_type_example(
143                        &f.ty,
144                        max_depth,
145                        schemas,
146                        current_depth + 1,
147                        indent_level + 1,
148                    );
149                    format!("{}{}: {}", inner_indent, f.name, value)
150                })
151                .collect();
152
153            if field_examples.is_empty() {
154                format!("{} {{}}", variant.name)
155            } else {
156                let indent = "    ".repeat(indent_level);
157                format!(
158                    "{}(\n{},\n{})",
159                    variant.name,
160                    field_examples.join(",\n"),
161                    indent
162                )
163            }
164        }
165    }
166}
167
168/// Build a lookup map from type path to schema.
169pub fn build_schema_map(schemas: &[DiscoveredSchema]) -> HashMap<&str, &Schema> {
170    schemas
171        .iter()
172        .map(|s| (s.type_path.as_str(), &s.schema))
173        .collect()
174}
175
176#[cfg(test)]
177mod tests {
178    use ahash::HashMapExt;
179    use ron2::schema::Field;
180
181    use super::*;
182
183    #[test]
184    fn test_generate_primitive_examples() {
185        let schemas = HashMap::new();
186        assert_eq!(
187            generate_type_example(&TypeKind::Bool, 2, &schemas, 0, 0),
188            "true"
189        );
190        assert_eq!(
191            generate_type_example(&TypeKind::String, 2, &schemas, 0, 0),
192            "\"...\""
193        );
194        assert_eq!(
195            generate_type_example(&TypeKind::I32, 2, &schemas, 0, 0),
196            "0"
197        );
198    }
199
200    #[test]
201    fn test_generate_compound_examples() {
202        let schemas = HashMap::new();
203
204        let option_string = TypeKind::Option(Box::new(TypeKind::String));
205        assert_eq!(
206            generate_type_example(&option_string, 2, &schemas, 0, 0),
207            "Some(\"...\")"
208        );
209
210        let list_i32 = TypeKind::List(Box::new(TypeKind::I32));
211        assert_eq!(generate_type_example(&list_i32, 2, &schemas, 0, 0), "[0]");
212    }
213
214    #[test]
215    fn test_generate_struct_example() {
216        let schemas = HashMap::new();
217
218        let struct_kind = TypeKind::Struct {
219            fields: vec![
220                Field::new("name", TypeKind::String),
221                Field::new("age", TypeKind::I32),
222            ],
223        };
224
225        let example = generate_type_example(&struct_kind, 2, &schemas, 0, 0);
226        assert!(example.contains("name: \"...\""));
227        assert!(example.contains("age: 0"));
228    }
229
230    #[test]
231    fn test_generate_enum_example() {
232        let schemas = HashMap::new();
233
234        let enum_kind = TypeKind::Enum {
235            variants: vec![
236                Variant::unit("Low"),
237                Variant::unit("Medium"),
238                Variant::unit("High"),
239            ],
240        };
241
242        let example = generate_type_example(&enum_kind, 2, &schemas, 0, 0);
243        assert_eq!(example, "Low");
244    }
245}