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            {
106                return generate_type_example(
107                    &resolved.kind,
108                    max_depth,
109                    schemas,
110                    current_depth + 1,
111                    indent_level,
112                );
113            }
114            // Use short name as placeholder
115            let short_name = path.split("::").last().unwrap_or(path);
116            format!("/* {} */", short_name)
117        }
118    }
119}
120
121fn generate_variant_example(
122    variant: &Variant,
123    max_depth: usize,
124    schemas: &HashMap<&str, &Schema>,
125    current_depth: usize,
126    indent_level: usize,
127) -> String {
128    match &variant.kind {
129        VariantKind::Unit => variant.name.clone(),
130        VariantKind::Tuple(types) => {
131            let items: Vec<_> = types
132                .iter()
133                .map(|t| generate_type_example(t, max_depth, schemas, current_depth, indent_level))
134                .collect();
135            format!("{}({})", variant.name, items.join(", "))
136        }
137        VariantKind::Struct(fields) => {
138            let inner_indent = "    ".repeat(indent_level + 1);
139            let field_examples: Vec<_> = fields
140                .iter()
141                .filter(|f| !f.optional)
142                .map(|f| {
143                    let value = generate_type_example(
144                        &f.ty,
145                        max_depth,
146                        schemas,
147                        current_depth + 1,
148                        indent_level + 1,
149                    );
150                    format!("{}{}: {}", inner_indent, f.name, value)
151                })
152                .collect();
153
154            if field_examples.is_empty() {
155                format!("{} {{}}", variant.name)
156            } else {
157                let indent = "    ".repeat(indent_level);
158                format!(
159                    "{}(\n{},\n{})",
160                    variant.name,
161                    field_examples.join(",\n"),
162                    indent
163                )
164            }
165        }
166    }
167}
168
169/// Build a lookup map from type path to schema.
170pub fn build_schema_map(schemas: &[DiscoveredSchema]) -> HashMap<&str, &Schema> {
171    schemas
172        .iter()
173        .map(|s| (s.type_path.as_str(), &s.schema))
174        .collect()
175}
176
177#[cfg(test)]
178mod tests {
179    use ahash::HashMapExt;
180    use ron2::schema::Field;
181
182    use super::*;
183
184    #[test]
185    fn test_generate_primitive_examples() {
186        let schemas = HashMap::new();
187        assert_eq!(
188            generate_type_example(&TypeKind::Bool, 2, &schemas, 0, 0),
189            "true"
190        );
191        assert_eq!(
192            generate_type_example(&TypeKind::String, 2, &schemas, 0, 0),
193            "\"...\""
194        );
195        assert_eq!(
196            generate_type_example(&TypeKind::I32, 2, &schemas, 0, 0),
197            "0"
198        );
199    }
200
201    #[test]
202    fn test_generate_compound_examples() {
203        let schemas = HashMap::new();
204
205        let option_string = TypeKind::Option(Box::new(TypeKind::String));
206        assert_eq!(
207            generate_type_example(&option_string, 2, &schemas, 0, 0),
208            "Some(\"...\")"
209        );
210
211        let list_i32 = TypeKind::List(Box::new(TypeKind::I32));
212        assert_eq!(generate_type_example(&list_i32, 2, &schemas, 0, 0), "[0]");
213    }
214
215    #[test]
216    fn test_generate_struct_example() {
217        let schemas = HashMap::new();
218
219        let struct_kind = TypeKind::Struct {
220            fields: vec![
221                Field::new("name", TypeKind::String),
222                Field::new("age", TypeKind::I32),
223            ],
224        };
225
226        let example = generate_type_example(&struct_kind, 2, &schemas, 0, 0);
227        assert!(example.contains("name: \"...\""));
228        assert!(example.contains("age: 0"));
229    }
230
231    #[test]
232    fn test_generate_enum_example() {
233        let schemas = HashMap::new();
234
235        let enum_kind = TypeKind::Enum {
236            variants: vec![
237                Variant::unit("Low"),
238                Variant::unit("Medium"),
239                Variant::unit("High"),
240            ],
241        };
242
243        let example = generate_type_example(&enum_kind, 2, &schemas, 0, 0);
244        assert_eq!(example, "Low");
245    }
246}