1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
use crate::swagger20;

pub(crate) fn generate(
    mut writer: impl std::io::Write,
    type_name: &str,
    generics: super::Generics<'_>,
    definition_path: &swagger20::DefinitionPath,
    definition: &swagger20::Schema,
    schema_feature: Option<&str>,
    map_namespace: &impl crate::MapNamespace,
) -> Result<(), crate::Error> {
    let local = crate::map_namespace_local_to_string(map_namespace)?;

    let type_generics_impl = generics.type_part.map(|part| format!("<{part}>")).unwrap_or_default();
    let type_generics_type = generics.type_part.map(|part| format!("<{part}>")).unwrap_or_default();
    let type_generics_where = generics.where_part.map(|part| format!(" where {part}")).unwrap_or_default();

    let cfg = schema_feature.map_or_else(String::new, |schema_feature| format!("#[cfg(feature = {schema_feature:?})]\n"));

    let mut schema = String::new();

    gen_schema(&mut schema, definition, &local, map_namespace, 1)?;

    writeln!(
        writer,
        include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/impl_schema.rs")),
        local = local,
        cfg = cfg,
        type_name = type_name,
        type_generics_impl = type_generics_impl,
        type_generics_type = type_generics_type,
        type_generics_where = type_generics_where,
        definition_path = &**definition_path,
        schema = schema,
    )?;

    Ok(())
}

fn gen_schema(
    out: &mut String,
    definition: &swagger20::Schema,
    local: &str,
    map_namespace: &impl crate::MapNamespace,
    depth: usize,
) -> Result<(), crate::Error> {
    use std::fmt::Write;

    let indent = "    ".repeat(depth + 1);

    writeln!(out, "{indent}{local}schemars::schema::Schema::Object({local}schemars::schema::SchemaObject {{")?;

    if let Some(description) = &definition.description {
        writeln!(out, "{indent}    metadata: Some(Box::new({local}schemars::schema::Metadata {{")?;
        writeln!(out, "{indent}        description: Some({description:?}.to_owned()),")?;
        writeln!(out, "{indent}        ..Default::default()")?;
        writeln!(out, "{indent}    }})),")?;
    }

    match &definition.kind {
        swagger20::SchemaKind::Properties(properties) => {
            writeln!(out,
                "{indent}    instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::Object))),")?;
            writeln!(out, "{indent}    object: Some(Box::new({local}schemars::schema::ObjectValidation {{")?;

            let mut required_props = String::new();
            let mut props = String::new();
            for (name, (schema, required)) in properties {
                if *required {
                    writeln!(required_props, "{indent}            {:?}.to_owned(),", &**name)?;
                }

                match &schema.kind {
                    swagger20::SchemaKind::Properties(_) => unreachable!("unexpected nested properties"),

                    swagger20::SchemaKind::Ref(ref_path) => {
                        writeln!(props, "{indent}            (")?;
                        writeln!(props, "{indent}                {:?}.to_owned(),", &**name)?;

                        let type_name = crate::get_fully_qualified_type_name(ref_path, map_namespace);

                        if let Some(description) = &schema.description {
                            // Override the subschema's description
                            writeln!(props, "{indent}                {{")?;
                            writeln!(props, "{indent}                    let mut schema_obj = __gen.subschema_for::<{type_name}>().into_object();")?;
                            writeln!(props, "{indent}                    schema_obj.metadata = Some(Box::new({local}schemars::schema::Metadata {{")?;
                            writeln!(props, "{indent}                        description: Some({description:?}.to_owned()),")?;
                            writeln!(props, "{indent}                        ..Default::default()")?;
                            writeln!(props, "{indent}                    }}));")?;
                            writeln!(props, "{indent}                    {local}schemars::schema::Schema::Object(schema_obj)")?;
                            writeln!(props, "{indent}                }},")?;
                        }
                        else {
                            writeln!(props, "{indent}                __gen.subschema_for::<{type_name}>(),")?;
                        }

                        writeln!(props, "{indent}            ),")?;
                    },

                    swagger20::SchemaKind::Ty(ty) => {
                        writeln!(props, "{indent}            (")?;
                        writeln!(props, "{indent}                {:?}.to_owned(),", &**name)?;
                        gen_type_as_schema_object(&mut props, ty, schema.description.as_deref(), local, map_namespace, depth)?;
                        writeln!(props, "{indent}            ),")?;
                    },
                }
            }

            if !props.is_empty() {
                writeln!(out, "{indent}        properties: [")?;
                write!(out, "{props}")?;
                writeln!(out, "{indent}        ].into(),")?;
            }

            if !required_props.is_empty() {
                writeln!(out, "{indent}        required: [")?;
                write!(out, "{required_props}")?;
                writeln!(out, "{indent}        ].into(),")?;
            }

            writeln!(out, "{indent}        ..Default::default()")?;
            writeln!(out, "{indent}    }})),")?;
        },

        swagger20::SchemaKind::Ty(ty) =>
            gen_type(out, ty, local, map_namespace, depth + 1)?,

        swagger20::SchemaKind::Ref(ref_path) => unreachable!("unexpected $ref {ref_path:?}"),
    }

    writeln!(out, "{indent}    ..Default::default()")?;
    writeln!(out, "{indent}}})")?;

    Ok(())
}

fn gen_type_as_schema_object(
    out: &mut String,
    ty: &swagger20::Type,
    description_override: Option<&str>,
    local: &str,
    map_namespace: &impl crate::MapNamespace,
    depth: usize,
) -> Result<(), crate::Error> {
    use std::fmt::Write;

    let indent = "    ".repeat(depth + 1);

    if let swagger20::Type::CustomResourceSubresources(v) = ty {
        let json_schema_props_type_name = crate::get_fully_qualified_type_name(&swagger20::RefPath {
            path: format!("io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.{v}.CustomResourceSubresources"),
            can_be_default: None,
        }, map_namespace);
        writeln!(out, "{indent}                __gen.subschema_for::<{json_schema_props_type_name}>(),")?;
    }
    else {
        writeln!(out, "{indent}                {local}schemars::schema::Schema::Object({local}schemars::schema::SchemaObject {{")?;
        if let Some(description) = description_override {
            writeln!(out, "{indent}                    metadata: Some(Box::new({local}schemars::schema::Metadata {{")?;
            writeln!(out, "{indent}                        description: Some({description:?}.to_owned()),")?;
            writeln!(out, "{indent}                        ..Default::default()")?;
            writeln!(out, "{indent}                    }})),")?;
        }

        gen_type(out, ty, local, map_namespace, depth + 5)?;

        writeln!(out, "{indent}                    ..Default::default()")?;
        writeln!(out, "{indent}                }}),")?;
    }

    Ok(())
}

fn gen_type(
    out: &mut String,
    ty: &swagger20::Type,
    local: &str,
    map_namespace: &impl crate::MapNamespace,
    depth: usize,
) -> Result<(), crate::Error> {
    use std::fmt::Write;

    let indent = "    ".repeat(depth + 1);

    match ty {
        swagger20::Type::Any =>
            writeln!(out,
                "{indent}instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::Object))),")?,

        swagger20::Type::JsonSchemaPropsOr(v, swagger20::JsonSchemaPropsOr::Array) => {
            let json_schema_props_type_name = crate::get_fully_qualified_type_name(&swagger20::RefPath {
                path: format!("io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.{v}.JSONSchemaProps"),
                can_be_default: None,
            }, map_namespace);

            writeln!(out, "{indent}subschemas: Some(Box::new({local}schemars::schema::SubschemaValidation {{")?;
            writeln!(out, "{indent}    one_of: Some(vec![")?;

            writeln!(out, "{indent}        __gen.subschema_for::<{json_schema_props_type_name}>(),")?;

            writeln!(out, "{indent}        {local}schemars::schema::Schema::Object({local}schemars::schema::SchemaObject {{")?;
            writeln!(out,
                "{indent}            instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::Array))),")?;
            writeln!(out, "{indent}            array: Some(Box::new({local}schemars::schema::ArrayValidation {{")?;
            writeln!(out,
                "{indent}                items: Some({local}schemars::schema::SingleOrVec::Single(Box::new(__gen.subschema_for::<{json_schema_props_type_name}>()))),")?;
            writeln!(out, "{indent}                ..Default::default()")?;
            writeln!(out, "{indent}            }})),")?;
            writeln!(out, "{indent}            ..Default::default()")?;
            writeln!(out, "{indent}        }}),")?;

            writeln!(out, "{indent}    ]),")?;
            writeln!(out, "{indent}    ..Default::default()")?;
            writeln!(out, "{indent}}})),")?;
        }

        swagger20::Type::JsonSchemaPropsOr(v, swagger20::JsonSchemaPropsOr::Bool) => {
            let json_schema_props_type_name = crate::get_fully_qualified_type_name(&swagger20::RefPath {
                path: format!("io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.{v}.JSONSchemaProps"),
                can_be_default: None,
            }, map_namespace);

            writeln!(out, "{indent}subschemas: Some(Box::new({local}schemars::schema::SubschemaValidation {{")?;
            writeln!(out, "{indent}    one_of: Some(vec![")?;

            writeln!(out, "{indent}        __gen.subschema_for::<{json_schema_props_type_name}>(),")?;

            writeln!(out, "{indent}        {local}schemars::schema::Schema::Object({local}schemars::schema::SchemaObject {{")?;
            writeln!(out,
                "{indent}            instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::Boolean))),")?;
            writeln!(out, "{indent}            ..Default::default()")?;
            writeln!(out, "{indent}        }}),")?;

            writeln!(out, "{indent}    ]),")?;
            writeln!(out, "{indent}    ..Default::default()")?;
            writeln!(out, "{indent}}})),")?;
        }

        swagger20::Type::JsonSchemaPropsOr(v, swagger20::JsonSchemaPropsOr::StringArray) => {
            let json_schema_props_type_name = crate::get_fully_qualified_type_name(&swagger20::RefPath {
                path: format!("io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.{v}.JSONSchemaProps"),
                can_be_default: None,
            }, map_namespace);

            writeln!(out, "{indent}subschemas: Some(Box::new({local}schemars::schema::SubschemaValidation {{")?;
            writeln!(out, "{indent}    one_of: Some(vec![")?;

            writeln!(out, "{indent}        __gen.subschema_for::<{json_schema_props_type_name}>(),")?;


            writeln!(out, "{indent}        {local}schemars::schema::Schema::Object({local}schemars::schema::SchemaObject {{")?;
            writeln!(out,
                "{indent}            instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::Array))),")?;
            writeln!(out, "{indent}            array: Some(Box::new({local}schemars::schema::ArrayValidation {{")?;

            writeln!(out, "{indent}                items: Some({local}schemars::schema::SingleOrVec::Single(Box::new(")?;
            writeln!(out, "{indent}                    {local}schemars::schema::Schema::Object({local}schemars::schema::SchemaObject {{")?;
            writeln!(out,
                "{indent}                        instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::String))),")?;
            writeln!(out, "{indent}                        ..Default::default()")?;
            writeln!(out, "{indent}                    }}),")?;
            writeln!(out, "{indent}                ))),")?;

            writeln!(out, "{indent}                ..Default::default()")?;
            writeln!(out, "{indent}            }})),")?;
            writeln!(out, "{indent}            ..Default::default()")?;
            writeln!(out, "{indent}        }}),")?;


            writeln!(out, "{indent}    ]),")?;
            writeln!(out, "{indent}    ..Default::default()")?;
            writeln!(out, "{indent}}})),")?;
        }

        swagger20::Type::Array { items } => {
            writeln!(out,
                "{indent}instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::Array))),")?;
            writeln!(out, "{indent}array: Some(Box::new({local}schemars::schema::ArrayValidation {{")?;
            if let swagger20::SchemaKind::Ref(ref_path) = &items.kind {
                writeln!(out,
                    "{indent}    items: Some({local}schemars::schema::SingleOrVec::Single(Box::new(__gen.subschema_for::<{}>()))),",
                    crate::get_fully_qualified_type_name(ref_path, map_namespace))?;
            }
            else {
                writeln!(out, "{indent}    items: Some({local}schemars::schema::SingleOrVec::Single(Box::new(")?;
                gen_schema(out, items, local, map_namespace, depth + 2)?;
                writeln!(out, "{indent}    ))),")?;
            }
            writeln!(out, "{indent}    ..Default::default()")?;
            writeln!(out, "{indent}}})),")?;
        }

        swagger20::Type::Boolean => {
            writeln!(out,
                "{indent}instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::Boolean))),")?;
        }

        swagger20::Type::Integer { format } => {
            writeln!(out,
                "{indent}instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::Integer))),")?;
            let format = match format {
                swagger20::IntegerFormat::Int32 => {
                    "int32"
                }
                swagger20::IntegerFormat::Int64 => {
                    "int64"
                }
            };
            writeln!(out, "{indent}format: Some({format:?}.to_owned()),")?;
        }

        swagger20::Type::Number { format } => {
            writeln!(out,
                "{indent}instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::Number))),")?;

            match format {
                swagger20::NumberFormat::Double => {
                    writeln!(out, r#"{indent}format: Some("double".to_owned()),"#)?;
                }
            }
        }

        swagger20::Type::Object { additional_properties } => {
            writeln!(out,
                "{indent}instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::Object))),")?;
            writeln!(out, "{indent}object: Some(Box::new({local}schemars::schema::ObjectValidation {{")?;
            if let swagger20::SchemaKind::Ref(ref_path) = &additional_properties.kind {
                writeln!(out,
                    "{indent}    additional_properties: Some(Box::new(__gen.subschema_for::<{}>())),",
                    crate::get_fully_qualified_type_name(ref_path, map_namespace))?;
            }
            else {
                writeln!(out, "{indent}    additional_properties: Some(Box::new(")?;
                gen_schema(out, additional_properties, local, map_namespace, depth + 2)?;
                writeln!(out, "{indent}    )),")?;
            }
            writeln!(out, "{indent}    ..Default::default()")?;
            writeln!(out, "{indent}}})),")?;
        }

        swagger20::Type::String { format } => {
            writeln!(out,
                "{indent}instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::String))),")?;
            match format {
                Some(swagger20::StringFormat::Byte) => {
                    writeln!(out, r#"{indent}format: Some("byte".to_owned()),"#)?;
                },
                Some(swagger20::StringFormat::DateTime) => {
                    writeln!(out, r#"{indent}format: Some("date-time".to_owned()),"#)?;
                },
                None => (),
            }
        }

        swagger20::Type::IntOrString => {
            writeln!(out, r#"{indent}extensions: ["#)?;
            writeln!(out, r#"{indent}    ("x-kubernetes-int-or-string".to_owned(), crate::serde_json::Value::Bool(true)),"#)?;
            writeln!(out, r#"{indent}].into(),"#)?;
        }

        swagger20::Type::Patch => {
            writeln!(out,
                "{indent}instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::Object))),")?;
        }

        swagger20::Type::WatchEvent(ref_path) => {
            writeln!(out,
                "{indent}instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::Object))),")?;
            writeln!(out, "{indent}object: Some(Box::new({local}schemars::schema::ObjectValidation {{")?;
            writeln!(out, "{indent}    properties: [")?;
            writeln!(out, "{indent}        (")?;
            writeln!(out, r#"{indent}            "object".to_owned(),"#)?;
            writeln!(out,  "{indent}            __gen.subschema_for::<{}>(),", crate::get_fully_qualified_type_name(ref_path, map_namespace))?;
            writeln!(out, "{indent}        ),")?;

            writeln!(out, "{indent}        (")?;
            writeln!(out, r#"{indent}            "type".to_owned(),"#)?;
            writeln!(out, "{indent}            {local}schemars::schema::Schema::Object({local}schemars::schema::SchemaObject {{")?;
            writeln!(out,
                "{indent}                instance_type: Some({local}schemars::schema::SingleOrVec::Single(Box::new({local}schemars::schema::InstanceType::String))),")?;
            writeln!(out, "{indent}                ..Default::default()")?;
            writeln!(out, "{indent}            }}),")?;
            writeln!(out, "{indent}        ),")?;
            writeln!(out, "{indent}    ].into(),")?;

            writeln!(out, "{indent}    required: [")?;
            writeln!(out, r#"{indent}        "object".to_owned(),"#)?;
            writeln!(out, r#"{indent}        "type".to_owned(),"#)?;
            writeln!(out, "{indent}    ].into(),")?;

            writeln!(out, "{indent}    ..Default::default()")?;
            writeln!(out, "{indent}}})),")?;
        }

        _ => unreachable!("cannot generate schema for type {ty:?}"),
    }

    Ok(())
}