Skip to main content

paperclip_ng/v3/
property.rs

1use std::{collections::HashMap, convert::TryInto, fmt::Display, ops::Deref, rc::Rc};
2
3use heck::{ToSnakeCase, ToUpperCamelCase};
4use ramhorns_derive::Content;
5
6use log::{error, trace};
7
8/// The various openapi v3 property data types.
9#[derive(Clone, Debug)]
10pub(crate) enum PropertyDataType {
11    Unknown,
12    Resolved(String, Option<String>),
13    Any,
14    RawString,
15    String(openapiv3::StringType),
16    Enum(String, String),
17    Boolean,
18    Integer(openapiv3::IntegerType),
19    Number(openapiv3::NumberType),
20    Model(String),
21    DiscModel(String, String),
22    Map(Box<PropertyDataType>, Box<PropertyDataType>),
23    Array(Box<PropertyDataType>),
24    Empty,
25}
26
27impl Display for PropertyDataType {
28    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
29        write!(f, "{}", self.as_str())
30    }
31}
32
33impl PropertyDataType {
34    fn as_str(&self) -> &str {
35        match self {
36            PropertyDataType::Unknown => "Unkown",
37            PropertyDataType::Resolved(inner, _) => inner.as_ref(),
38            PropertyDataType::Array(inner) => inner.as_str(),
39            PropertyDataType::Any => "Any",
40            PropertyDataType::Map(_, _) => "Map",
41            PropertyDataType::RawString => "String",
42            PropertyDataType::String(_) => "String",
43            PropertyDataType::Enum(_, _) => "Enum",
44            PropertyDataType::Boolean => "bool",
45            PropertyDataType::Integer(_) => "integer",
46            PropertyDataType::Number(_) => "number",
47            PropertyDataType::Model(inner) => inner.as_str(),
48            PropertyDataType::DiscModel(_, _) => "Disc",
49            PropertyDataType::Empty => "Empty",
50        }
51    }
52    fn format(&self) -> Option<&String> {
53        match self {
54            PropertyDataType::Resolved(_, format) => format.as_ref(),
55            _ => None,
56        }
57    }
58    fn resolve(&mut self, data_type: &str) {
59        self.set_if_unresolved(Self::Resolved(data_type.into(), None));
60    }
61    fn resolve_format<T: Into<String>>(&mut self, data_type: &str, format: T) {
62        self.set_if_unresolved(Self::Resolved(data_type.into(), Some(format.into())));
63    }
64    fn resolve_format_opt(&mut self, data_type: &str, format: Option<String>) {
65        self.set_if_unresolved(Self::Resolved(data_type.into(), format));
66    }
67    fn set_string(&mut self, data_type: &openapiv3::StringType) {
68        self.set_if_unresolved(Self::String(data_type.clone()));
69    }
70    fn set_array(&mut self, data_type: &Self) {
71        self.set_if_unresolved(Self::Array(Box::new(data_type.clone())));
72    }
73    fn set_boolean(&mut self) {
74        self.set_if_unresolved(Self::Boolean);
75    }
76    fn set_integer(&mut self, data_type: &openapiv3::IntegerType) {
77        self.set_if_unresolved(Self::Integer(data_type.clone()));
78    }
79    fn set_number(&mut self, data_type: &openapiv3::NumberType) {
80        self.set_if_unresolved(Self::Number(data_type.clone()));
81    }
82    fn set_model(&mut self, data_type: &str) {
83        self.set_if_unresolved(Self::Model(data_type.to_string()));
84    }
85    fn set_disc_model(&mut self, parent: String, name: &str) {
86        self.set_if_unresolved(Self::DiscModel(parent, name.to_string()));
87    }
88    fn set_map(&mut self, key: &Self, value: &Self) {
89        self.set_if_unresolved(Self::Map(Box::new(key.clone()), Box::new(value.clone())));
90    }
91    fn set_enum(&mut self, name: &str, data_type: &str) {
92        self.set_if_unresolved(Self::Enum(name.to_string(), data_type.to_string()));
93    }
94    fn set_any(&mut self) {
95        *self = Self::Any;
96    }
97    fn set_if_unresolved(&mut self, to: Self) {
98        if !matches!(self, Self::Resolved(_, _)) {
99            *self = to;
100        }
101    }
102}
103
104// todo: bump msrv and fixup
105#[allow(clippy::derivable_impls)]
106impl Default for PropertyDataType {
107    fn default() -> Self {
108        Self::Unknown
109    }
110}
111
112impl ramhorns::Content for PropertyDataType {
113    #[inline]
114    fn is_truthy(&self) -> bool {
115        !self.as_str().is_empty()
116    }
117
118    #[inline]
119    fn capacity_hint(&self, _tpl: &ramhorns::Template) -> usize {
120        self.as_str().len()
121    }
122
123    #[inline]
124    fn render_escaped<E: ramhorns::encoding::Encoder>(
125        &self,
126        encoder: &mut E,
127    ) -> Result<(), E::Error> {
128        encoder.write_escaped(self.as_str())
129    }
130
131    #[inline]
132    fn render_unescaped<E: ramhorns::encoding::Encoder>(
133        &self,
134        encoder: &mut E,
135    ) -> Result<(), E::Error> {
136        encoder.write_unescaped(self.as_str())
137    }
138}
139
140/// A list of properties.
141pub(crate) type Properties = Vec<Property>;
142
143/// An OpenApiV3 property of a Schema Object.
144/// https://spec.openapis.org/oas/v3.0.3#properties
145/// Including fixed fields, composition, etc.
146/// These fields are used for both managing the template generation as well as input for
147/// the templates themselves.
148#[derive(Default, Content, Clone, Debug)]
149#[ramhorns(rename_all = "camelCase")]
150pub(crate) struct Property {
151    // The schema name as written in the OpenAPI document.
152    name: String,
153
154    // The language-specific name of the "class" that implements this schema.
155    // The name of the class is derived from the OpenAPI schema name with formatting rules applied.
156    // The classname is derived from the OpenAPI schema name, with sanitization and escaping rules
157    // applied.
158    pub classname: String,
159    schema_name: String,
160    class_filename: String,
161
162    base_name: String,
163    enum_name: Option<String>,
164    // The value of the 'title' attribute in the OpenAPI document.
165    title: Option<String>,
166    description: Option<String>,
167    example: Option<String>,
168    class_var_name: String,
169    model_json: String,
170    data_type: PropertyDataType,
171    data_format: String,
172    /// The type_ coming from component schema.
173    type_: String,
174
175    /// Booleans for is_$-like type checking.
176    is_string: bool,
177    is_integer: bool,
178    is_long: bool,
179    is_number: bool,
180    is_numeric: bool,
181    is_float: bool,
182    is_double: bool,
183    is_date: bool,
184    is_date_time: bool,
185    is_password: bool,
186    is_decimal: bool,
187    is_binary: bool,
188    is_byte: bool,
189    is_short: bool,
190    is_unbounded_integer: bool,
191    is_primitive_type: bool,
192    is_boolean: bool,
193    is_uuid: bool,
194    is_uri: bool,
195    is_any_type: bool,
196    is_enum: bool,
197    is_array: bool,
198    is_container: bool,
199    is_map: bool,
200    is_null: bool,
201    is_var: bool,
202
203    /// Indicates whether additional properties has defined this as an Any type.
204    additional_properties_is_any_type: bool,
205
206    /// If Self is an object, these are all its child properties.
207    vars: Properties,
208    /// And this? Inludes the parent properties? What does this mean?
209    all_vars: Properties,
210
211    /// These could be "special" ramhorn methods rather than fields to avoid copy.
212    /// Only the required properties.
213    required_vars: Properties,
214    /// Only the optional properties.
215    optional_vars: Properties,
216    // Only the read-only properties.
217    read_only_vars: Properties,
218    // The read/write properties.
219    read_write_vars: Properties,
220    /// The Self's parent properties.
221    parent_vars: Properties,
222
223    /// If this is an enum, all the allowed values.
224    allowable_values: HashMap<String, Vec<EnumValue>>,
225
226    /// If this is an array, the inner property of each index.
227    items: Option<Box<Property>>,
228
229    /// Indicates whether Self has child variables or not.
230    has_vars: bool,
231    /// Indicates whether there are enpty vars? What does this mean?
232    empty_vars: bool,
233    has_enums: bool,
234    /// Validation rules? Like patterns?
235    has_validation: bool,
236    /// Indicates the OAS schema specifies "nullable: true".
237    is_nullable: bool,
238    /// Indicates the type has at least one required property.
239    has_required: bool,
240    /// Indicates the type has at least one optional property.
241    has_optional: bool,
242    /// Indicates wether we have children vars? Or are these for inline schemas/properties?
243    has_children: bool,
244
245    is_deprecated: bool,
246    has_only_read_only: bool,
247    required: bool,
248    max_properties: Option<usize>,
249    min_properties: Option<usize>,
250    unique_items: bool,
251    max_items: Option<usize>,
252    min_items: Option<usize>,
253    max_length: Option<usize>,
254    min_length: Option<usize>,
255    exclusive_minimum: bool,
256    exclusive_maximum: bool,
257    minimum: Option<String>,
258    maximum: Option<String>,
259    pattern: Option<String>,
260
261    /// If we are a schema defined model?
262    is_model: bool,
263    /// If we are a component model defined in the root component schemas: #/components/schemas.
264    is_component_model: bool,
265
266    one_of: Properties,
267    all_of: Properties,
268
269    /// Inline models discovered through the schema of this very model.
270    discovered_props: Rc<Properties>,
271
272    /// The parent property of this property, if this property is defined "inline" as an Item or a class member or item.
273    parent: Option<Rc<Property>>,
274
275    vendor_extensions: HashMap<String, String>,
276}
277
278impl Display for Property {
279    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
280        write!(
281            f,
282            "{}/{}/{}.rs",
283            self.data_type(),
284            self.classname,
285            self.class_filename
286        )
287    }
288}
289
290impl Property {
291    /// Mutate the inner properties with the OpenAPI `openapiv3::SchemaData`.
292    pub fn with_data(mut self, data: &openapiv3::SchemaData) -> Self {
293        self.is_null = data.nullable;
294        self.is_nullable = data.nullable;
295        self.is_deprecated = data.deprecated;
296        self.title = data.title.clone();
297        self.description = data.description.as_ref().map(|s| s.replace('\n', " "));
298        self.example = data.example.as_ref().map(ToString::to_string);
299        self.vendor_extensions = data
300            .extensions
301            .iter()
302            .map(|(k, v)| (k.clone(), v.to_string()))
303            .collect();
304        self
305    }
306    /// Set wether the property is a model or not.
307    pub fn with_model(mut self, model: bool) -> Self {
308        self.is_model = model;
309        self
310    }
311    /// Set wether the property is a component model or not.
312    pub fn with_component_model(mut self, root_model: bool) -> Self {
313        if root_model {
314            self.is_component_model = true;
315        }
316        self
317    }
318    /// Get a reference to the property type.
319    pub fn type_ref(&self) -> &str {
320        &self.type_
321    }
322    /// Get the property data type.
323    pub fn data_type(&self) -> String {
324        self.data_type.to_string()
325    }
326    /// Get the property data format.
327    pub fn data_format(&self) -> String {
328        self.data_type.format().map(Into::into).unwrap_or_default()
329    }
330    /// Get the class filename, if the property is a model.
331    pub fn filename(&self) -> &str {
332        self.class_filename.as_str()
333    }
334    /// Set the property data type.
335    pub fn with_data_property(mut self, type_: &PropertyDataType) -> Self {
336        self.data_type = type_.clone();
337        self
338    }
339    /// Set the model type.
340    pub fn with_model_type(mut self, type_: &str) -> Self {
341        match self.parent() {
342            Some(parent) if type_.is_empty() => {
343                let parent_type = parent.type_.clone();
344                self.data_type.set_disc_model(parent_type, &self.name);
345            }
346            _ => {
347                self.data_type.set_model(type_);
348            }
349        }
350        self
351    }
352    /// Set the data type Any, and if there's additional properties.
353    fn with_data_type_any(mut self, is_add_props: bool) -> Self {
354        self.data_type.set_any();
355        self.is_any_type = true;
356        self.is_model = false;
357        self.is_container = true;
358        self.additional_properties_is_any_type = is_add_props;
359        self
360    }
361    /// Set the property type.
362    pub fn with_type(mut self, type_: &str) -> Self {
363        self.type_ = type_.to_string();
364        self
365    }
366    /// The property is an OpenAPI AllOf, composed of a single property.
367    /// (This is because multiple properties is not supported yet)
368    pub fn with_one_all_of(self, single: Property) -> Self {
369        self.with_name(&single.name)
370            .with_type(&single.type_)
371            .with_data_property(&single.data_type)
372            .with_model(true)
373            .with_parent(&Some(&single))
374            .with_all_of(vec![single])
375    }
376    fn with_all_of(mut self, all_of: Vec<Property>) -> Self {
377        self.all_of = all_of;
378        self
379    }
380    /// Get a reference to the list of properties discovered through this property.
381    fn discovered_props(&self) -> &Vec<Property> {
382        &self.discovered_props
383    }
384    /// Similar as `discovered_props` but filters for models and applied recursively.
385    pub fn discovered_models(&self) -> Vec<Property> {
386        self.discovered_props()
387            .iter()
388            .flat_map(|m| {
389                let mut v = m.discovered_models();
390                v.push(m.clone());
391                v
392            })
393            .filter(|p| !p.is_component_model && p.is_model && !p.is_all_of() && !p.is_enum)
394            .collect::<Vec<_>>()
395    }
396}
397impl From<&openapiv3::SchemaData> for Property {
398    fn from(data: &openapiv3::SchemaData) -> Self {
399        Self::default().with_data(data)
400    }
401}
402
403impl Property {
404    /// Create a `Property` from an OpenAPI schema, with some other information.
405    pub fn from_schema(
406        root: &super::OpenApiV3,
407        parent: Option<&Property>,
408        schema: &openapiv3::Schema,
409        name: Option<&str>,
410        type_: Option<&str>,
411    ) -> Self {
412        let name = name.unwrap_or_default();
413        let type_ = type_.unwrap_or_default();
414        trace!(
415            "PropertyFromSchema: {}/{}/{}",
416            name,
417            type_,
418            Self::schema_kind_str(schema)
419        );
420        let prop = Property::from(&schema.schema_data)
421            .with_name(name)
422            .with_parent(&parent)
423            .with_type(type_)
424            .with_component_model(root.contains_schema(type_));
425
426        prop.with_kind(root, schema, &schema.schema_kind, parent, name, type_)
427    }
428
429    fn schema_kind_str(schema: &openapiv3::Schema) -> &str {
430        match &schema.schema_kind {
431            openapiv3::SchemaKind::Type(_) => "type",
432            openapiv3::SchemaKind::OneOf { .. } => "oneOf",
433            openapiv3::SchemaKind::AllOf { .. } => "allOf",
434            openapiv3::SchemaKind::AnyOf { .. } => "anyOf",
435            openapiv3::SchemaKind::Not { .. } => "not",
436            openapiv3::SchemaKind::Any(..) => "any",
437        }
438    }
439
440    fn with_kind(
441        mut self,
442        root: &super::OpenApiV3,
443        schema: &openapiv3::Schema,
444        schema_kind: &openapiv3::SchemaKind,
445        parent: Option<&Self>,
446        name: &str,
447        type_: &str,
448    ) -> Self {
449        match schema_kind {
450            openapiv3::SchemaKind::Type(t) => match t {
451                openapiv3::Type::String(t) => self.with_string(root, t),
452                openapiv3::Type::Number(t) => self.with_number(root, t),
453                openapiv3::Type::Integer(t) => self.with_integer(root, t),
454                openapiv3::Type::Object(t) => self.with_model_type(type_).with_obj(root, t),
455                openapiv3::Type::Array(t) => self.with_array(root, t),
456                openapiv3::Type::Boolean(_) => {
457                    self.data_type.set_boolean();
458                    self.is_boolean = true;
459                    self.is_primitive_type = true;
460                    self
461                }
462            },
463            openapiv3::SchemaKind::OneOf { .. } => {
464                panic!("OneOf: {:#?} not implemented", schema);
465            }
466            openapiv3::SchemaKind::AllOf { all_of } if all_of.len() != 1 => {
467                unimplemented!()
468            }
469            openapiv3::SchemaKind::AllOf { all_of } => {
470                let first = all_of.first().unwrap();
471                let first_model = root
472                    .resolve_reference_or(first, parent, Some(name), None)
473                    .with_data(&schema.schema_data);
474                Self::from(&schema.schema_data).with_one_all_of(first_model)
475            }
476            openapiv3::SchemaKind::AnyOf { .. } => {
477                unimplemented!()
478            }
479            openapiv3::SchemaKind::Not { .. } => {
480                unimplemented!()
481            }
482            // In some cases, we get Any rather than a specific kind :(
483            // For more info: https://github.com/glademiller/openapiv3/pull/79
484            // todo: this needs a lot of tweaking...
485            openapiv3::SchemaKind::Any(any_schema) => match &any_schema.typ {
486                Some(typ) => match typ.as_str() {
487                    "bool" => {
488                        let kind = openapiv3::SchemaKind::Type(openapiv3::Type::Boolean(
489                            openapiv3::BooleanType {
490                                enumeration: vec![],
491                            },
492                        ));
493                        self.with_kind(root, schema, &kind, parent, name, type_)
494                    }
495                    "object" => self.with_model_type(type_).with_anyobj(root, any_schema),
496                    not_handled => {
497                        // See above, we must handle all types in the match :(
498                        error!("BUG - must handle {not_handled} data type as AnySchema");
499                        self.with_data_type_any(false)
500                    }
501                },
502                // not sure how to handle this? default to Any for now.
503                None => self.with_data_type_any(false),
504            },
505        }
506    }
507
508    fn assign_classnames(&mut self) {
509        if self.classname.is_empty() && self.is_model && !self.is_var {
510            let schema_name = self.data_type.as_str();
511            self.class_filename = schema_name.to_snake_case();
512            self.classname = schema_name.to_upper_camel_case();
513        }
514        self.assign_enumnames();
515    }
516    fn assign_varnames(&mut self) {
517        if !self.name.is_empty() {
518            self.name = self.name.to_snake_case();
519        }
520    }
521    fn assign_enumnames(&mut self) {
522        if self.is_enum {
523            self.enum_name = Some(self.data_type());
524        }
525    }
526    fn string_format_str(format: openapiv3::StringFormat) -> &'static str {
527        match format {
528            openapiv3::StringFormat::Date => "date",
529            openapiv3::StringFormat::DateTime => "date-time",
530            openapiv3::StringFormat::Password => "password",
531            openapiv3::StringFormat::Byte => "byte",
532            openapiv3::StringFormat::Binary => "binary",
533        }
534    }
535    // This can be provided for a way of custumizing the types.
536    fn post_process_dt(data_type: &mut PropertyDataType, is_decl: bool) {
537        match data_type.clone() {
538            PropertyDataType::Unknown => {}
539            PropertyDataType::Resolved(_, _) => {}
540            PropertyDataType::Any => data_type.resolve("serde_json::Value"),
541            PropertyDataType::RawString => data_type.resolve("String"),
542            PropertyDataType::String(str) => {
543                match str.format {
544                    openapiv3::VariantOrUnknownOrEmpty::Item(format) => {
545                        // todo: handle these formats
546                        data_type.resolve_format("String", Self::string_format_str(format));
547                    }
548                    openapiv3::VariantOrUnknownOrEmpty::Unknown(format) => match format.as_str() {
549                        "uuid" => data_type.resolve("uuid::Uuid"),
550                        "uri" => data_type.resolve("url::Url"),
551                        _ => data_type.resolve_format("String", format),
552                    },
553                    openapiv3::VariantOrUnknownOrEmpty::Empty => {
554                        data_type.resolve("String");
555                    }
556                }
557            }
558            PropertyDataType::Enum(name, type_) if !is_decl => {
559                let enum_ = if type_.is_empty() { name } else { type_ }.to_upper_camel_case();
560                data_type.resolve(&format!("crate::models::{enum_}"))
561            }
562            PropertyDataType::Enum(name, type_) => {
563                let enum_ = if type_.is_empty() { name } else { type_ }.to_upper_camel_case();
564                data_type.resolve(&enum_)
565            }
566            PropertyDataType::Boolean => data_type.resolve("bool"),
567            PropertyDataType::Integer(type_) => {
568                let (signed, mut bits, format) = match type_.format {
569                    openapiv3::VariantOrUnknownOrEmpty::Item(item) => match item {
570                        openapiv3::IntegerFormat::Int32 => (true, 32, Some("int32".into())),
571                        openapiv3::IntegerFormat::Int64 => (true, 64, Some("int64".into())),
572                    },
573                    openapiv3::VariantOrUnknownOrEmpty::Unknown(format) => match format.as_str() {
574                        "uint32" => (false, 32, Some(format)),
575                        "uint64" => (false, 64, Some(format)),
576                        "int16" => (true, 16, Some(format)),
577                        "uint16" => (false, 16, Some(format)),
578                        "int8" => (true, 8, Some(format)),
579                        "uint8" => (false, 8, Some(format)),
580                        _ => (true, 0, Some(format)),
581                    },
582                    _ => (true, 0, None),
583                };
584                let signed = type_.minimum.map(|m| m < 0).unwrap_or(signed);
585                if let Some(max) = type_.maximum {
586                    let r_bits = floor_log2(max.try_into().unwrap());
587                    if bits == 0 || bits > r_bits {
588                        bits = r_bits;
589                    }
590                }
591
592                // no format specified
593                let bits = if bits == 0 {
594                    "size".to_string()
595                } else {
596                    bits.to_string()
597                };
598
599                // todo: check min and max
600                data_type.resolve_format_opt(
601                    &format!("{}{}", if signed { "i" } else { "u" }, bits),
602                    format,
603                )
604            }
605            PropertyDataType::Number(type_) => {
606                data_type.resolve(match type_.format {
607                    openapiv3::VariantOrUnknownOrEmpty::Item(openapiv3::NumberFormat::Float) => {
608                        "f32"
609                    }
610                    openapiv3::VariantOrUnknownOrEmpty::Item(openapiv3::NumberFormat::Double) => {
611                        "f64"
612                    }
613                    openapiv3::VariantOrUnknownOrEmpty::Unknown(_) => "f64",
614                    openapiv3::VariantOrUnknownOrEmpty::Empty => "f64",
615                });
616            }
617            PropertyDataType::Model(model) if !is_decl => {
618                data_type.resolve(&format!("crate::models::{model}"))
619            }
620            PropertyDataType::Model(model) => data_type.resolve(&model),
621            PropertyDataType::DiscModel(parent, this) => {
622                let this = this.to_upper_camel_case();
623                let parent = parent.to_upper_camel_case();
624                if is_decl {
625                    data_type.resolve(&format!("{parent}{this}"));
626                } else {
627                    data_type.resolve(&format!("crate::models::{parent}{this}"));
628                }
629            }
630            PropertyDataType::Map(key, mut value) => {
631                Self::post_process_dt(&mut value, false);
632                data_type.resolve(&format!(
633                    "::std::collections::HashMap<{}, {}>",
634                    key.as_ref(),
635                    value.as_str()
636                ))
637            }
638            PropertyDataType::Array(mut inner) => {
639                Self::post_process_dt(&mut inner, is_decl);
640                data_type.resolve(&format!("Vec<{}>", inner.as_str()))
641            }
642            PropertyDataType::Empty => data_type.resolve("()"),
643        }
644    }
645    /// This is a specific template hack, basically pretends this is not an enum
646    /// preventing it from being declared in the same module as the property where it was defined.
647    pub(crate) fn uninline_enums(&mut self) {
648        if self.is_var && self.is_component_model && self.is_enum {
649            // this is a very specific template hack?
650            self.is_enum = false;
651        }
652    }
653    /// Processes the data type for usage.
654    /// Properties which are not discovered at the top (eg: discovered via reference schema) get
655    /// a code import prefix added to them.
656    pub fn post_process(mut self) -> Property {
657        self.post_process_refmut();
658        self
659    }
660    /// Process the data type for a non-declaration usage.
661    /// The property **will** get the code import prefix added.
662    pub fn post_process_data_type(mut self) -> Property {
663        Self::post_process_dt(&mut self.data_type, false);
664        self
665    }
666    fn post_process_refmut(&mut self) {
667        // 1. setup data type, eg: add crate::models:: prefix for import.
668        // This is not required if the type is declared in the same module which currently is only
669        // true for enums.
670        let mut is_decl = !self.is_var && !self.is_container;
671        if self.is_var && !self.is_component_model && self.is_enum {
672            is_decl = true;
673        }
674        Self::post_process_dt(&mut self.data_type, is_decl);
675
676        // 2. fixup classname/type of non-enums defined within a type using Item
677        self.assign_classnames();
678        // 3. setup var names to be snake case
679        self.assign_varnames();
680
681        // 4. Uninline enums to avoid inline code generation.
682        // todo: template itself should do this!?
683        self.uninline_enums();
684
685        // 5. apply the same logic for variables within this object.
686        for var in &mut self.vars {
687            var.post_process_refmut();
688        }
689        for var in &mut self.required_vars {
690            var.post_process_refmut();
691        }
692        for var in &mut self.optional_vars {
693            var.post_process_refmut();
694        }
695        for var in &mut self.all_vars {
696            var.post_process_refmut();
697        }
698        for var in &mut self.all_of {
699            var.post_process_refmut();
700        }
701        for var in &mut self.one_of {
702            var.post_process_refmut();
703        }
704        if let Some(item) = &mut self.items {
705            item.post_process_refmut();
706        }
707    }
708
709    fn parent(&self) -> Option<&Self> {
710        match &self.parent {
711            None => None,
712            Some(parent) => Some(parent.deref()),
713        }
714    }
715    /// Get a reference to the inner type of the collection.
716    pub fn items(&self) -> &Option<Box<Self>> {
717        &self.items
718    }
719    /// Extend property with a new name.
720    pub fn with_name(mut self, name: &str) -> Self {
721        self.name = name.to_string();
722        self.base_name = name.to_string();
723        self
724    }
725    /// Extend property with a new is_var boolean.
726    fn with_is_var(mut self, is_var: bool) -> Self {
727        self.is_var = is_var;
728        self
729    }
730    /// Get a reference to the schema's data type.
731    /// # Warning: will panic if there is no data type (bug).
732    pub fn schema(&self) -> &str {
733        if self.data_type.as_str().is_empty() {
734            panic!("Schema data type should not be empty! Schema: {:#?}", self);
735        }
736        self.data_type.as_str()
737    }
738    /// Extend property with a new is_var boolean.
739    pub fn with_required(mut self, required: bool) -> Self {
740        self.required = required;
741        self
742    }
743    /// Extend property with a new parent property.
744    pub fn with_parent(mut self, parent: &Option<&Self>) -> Self {
745        self.parent = parent.map(|p| Rc::new(p.clone()));
746        self
747    }
748    /// Check if the property is a model.
749    pub fn is_model(&self) -> bool {
750        self.is_model
751    }
752    /// Check if the property is a string.
753    pub fn is_string(&self) -> bool {
754        self.is_string
755    }
756    /// Check if the property is an array.
757    pub fn is_array(&self) -> bool {
758        self.is_array
759    }
760    /// Check if the property is a string uuid.
761    pub fn is_uuid(&self) -> bool {
762        self.is_uuid
763    }
764    /// Check if the property is a string uri.
765    pub fn is_uri(&self) -> bool {
766        self.is_uri
767    }
768    /// Check if the property is a container.
769    pub fn is_container(&self) -> bool {
770        self.is_container
771    }
772    /// Check if the property is of any type.
773    pub fn is_any_type(&self) -> bool {
774        self.is_any_type
775    }
776    /// Check if the property is a primitive type.
777    pub fn is_primitive_type(&self) -> bool {
778        self.is_primitive_type
779    }
780    /// Check if the property is an AllOf.
781    pub fn is_all_of(&self) -> bool {
782        !self.all_of.is_empty()
783    }
784    fn with_array(mut self, _root: &super::OpenApiV3, by: &openapiv3::ArrayType) -> Self {
785        self.items = by
786            .items
787            .clone()
788            .map(|i| _root.resolve_reference_or(&i.unbox(), Some(&self), None, None))
789            .map(|i| i.with_is_var(true))
790            .map(Box::new);
791        self.min_items = by.min_items;
792        self.max_items = by.max_items;
793        self.unique_items = by.unique_items;
794        self.is_array = true;
795        match &self.items {
796            Some(items) => {
797                self.data_type.set_array(&items.data_type);
798            }
799            None => {
800                panic!("BUG: an array without an inner type: {:?}", self);
801            }
802        }
803        self.is_container = true;
804        self
805    }
806    fn with_anyobj(mut self, root: &super::OpenApiV3, by: &openapiv3::AnySchema) -> Self {
807        self.min_properties = by.min_properties;
808        self.max_properties = by.max_properties;
809
810        let vars = by
811            .properties
812            .iter()
813            .map(|(k, v)| root.resolve_reference_or(&v.clone().unbox(), Some(&self), Some(k), None))
814            .map(|m| {
815                let required = by.required.contains(&m.name);
816                m.with_required(required)
817            })
818            .collect::<Vec<_>>();
819
820        let vars = vars
821            .into_iter()
822            .map(|p| p.with_is_var(true))
823            .collect::<Vec<_>>();
824
825        self.required_vars = vars
826            .iter()
827            .filter(|m| m.required)
828            .cloned()
829            .collect::<Vec<_>>();
830        self.optional_vars = vars
831            .iter()
832            .filter(|m| !m.required)
833            .cloned()
834            .collect::<Vec<_>>();
835        self.vars = vars;
836
837        let mut vars_ = self.vars.iter().filter(|p| !p.required).collect::<Vec<_>>();
838        if vars_.len() != self.vars.len() {
839            panic!("Not Supported - all vars of oneOf must be optional");
840        }
841
842        let one_of = &by.one_of;
843        one_of
844            .iter()
845            .flat_map(|p| p.as_item())
846            .map(|s| match &s.schema_kind {
847                openapiv3::SchemaKind::Any(schema) => schema,
848                _ => todo!(),
849            })
850            .filter(|o| o.required.len() == 1)
851            .for_each(|o| vars_.retain(|v| v.name != o.required[0]));
852
853        self.is_model = true;
854        self.one_of = vec![self.clone()];
855        self
856    }
857    fn with_obj(mut self, root: &super::OpenApiV3, by: &openapiv3::ObjectType) -> Self {
858        self.min_properties = by.min_properties;
859        self.max_properties = by.max_properties;
860
861        if let Some(props) = &by.additional_properties {
862            match props {
863                openapiv3::AdditionalProperties::Any(any) => {
864                    if *any {
865                        return self.with_data_type_any(*any);
866                    }
867                }
868                openapiv3::AdditionalProperties::Schema(ref_or) => match ref_or.deref() {
869                    openapiv3::ReferenceOr::Reference { reference } => {
870                        let inner = root.resolve_schema_name(None, reference);
871                        self.data_type
872                            .set_map(&PropertyDataType::RawString, &inner.data_type);
873                        self.discovered_props = Rc::new(vec![inner]);
874                        return self;
875                    }
876                    openapiv3::ReferenceOr::Item(item) => {
877                        let property = Self::from_schema(root, None, item, None, None);
878                        self.data_type
879                            .set_map(&PropertyDataType::RawString, &property.data_type);
880                        return self;
881                    }
882                },
883            }
884        }
885
886        if !root.resolving(&self) {
887            let vars = by
888                .properties
889                .iter()
890                .map(|(k, v)| {
891                    root.resolve_reference_or(&v.clone().unbox(), Some(&self), Some(k), None)
892                })
893                .map(|m| {
894                    let required = by.required.contains(&m.name);
895                    m.with_required(required)
896                })
897                .collect::<Vec<_>>();
898
899            if vars.is_empty() {
900                return self.with_data_type_any(false);
901            }
902            self.is_model = true;
903
904            self.discovered_props = Rc::new(vars.clone());
905            let vars = vars
906                .into_iter()
907                .map(|p| p.with_is_var(true))
908                .collect::<Vec<_>>();
909
910            self.required_vars = vars
911                .iter()
912                .filter(|m| m.required)
913                .cloned()
914                .collect::<Vec<_>>();
915            self.optional_vars = vars
916                .iter()
917                .filter(|m| !m.required)
918                .cloned()
919                .collect::<Vec<_>>();
920            self.vars = vars;
921        } else {
922            // it's a circular reference, we must be a model
923            self.is_model = true;
924        }
925
926        // if let Some(one_of) = &by.one_of {
927        //     let mut vars_ = self.vars.iter().filter(|p| !p.required).collect::<Vec<_>>();
928        //     if vars_.len() != self.vars.len() {
929        //         panic!("Not Supported - all vars of oneOf must be optional");
930        //     }
931        //     one_of
932        //         .iter()
933        //         .flat_map(|p| p.as_item())
934        //         .filter(|o| o.required.len() == 1)
935        //         .for_each(|o| vars_.retain(|v| v.name != o.required[0]));
936        //     if vars_.is_empty() {
937        //         self.one_of = vec![self.clone()];
938        //     } else {
939        //         panic!("OneOf with incorrect combination of required fields");
940        //     }
941        // }
942        self
943    }
944    fn with_integer(mut self, _root: &super::OpenApiV3, by: &openapiv3::IntegerType) -> Self {
945        self.exclusive_maximum = by.exclusive_maximum;
946        self.exclusive_minimum = by.exclusive_minimum;
947        self.minimum = by.minimum.map(|v| v.to_string());
948        self.maximum = by.maximum.map(|v| v.to_string());
949        self.is_integer = true;
950        self.is_primitive_type = true;
951        self.data_type.set_integer(by);
952        self
953    }
954    fn with_number(mut self, _root: &super::OpenApiV3, by: &openapiv3::NumberType) -> Self {
955        self.exclusive_maximum = by.exclusive_maximum;
956        self.exclusive_minimum = by.exclusive_minimum;
957        self.minimum = by.minimum.map(|v| v.to_string());
958        self.maximum = by.maximum.map(|v| v.to_string());
959        self.data_type.set_number(by);
960        self.is_primitive_type = true;
961        self
962    }
963    fn with_string(mut self, _root: &super::OpenApiV3, by: &openapiv3::StringType) -> Self {
964        self.pattern = by.pattern.clone();
965        self.has_enums = !by.enumeration.is_empty();
966        self.is_enum = self.has_enums;
967
968        self.min_length = by.min_length;
969        self.data_type.set_string(by);
970
971        match &by.format {
972            openapiv3::VariantOrUnknownOrEmpty::Item(item) => match item {
973                openapiv3::StringFormat::Date => self.is_date = true,
974                openapiv3::StringFormat::DateTime => self.is_date_time = true,
975                openapiv3::StringFormat::Password => self.is_date = true,
976                openapiv3::StringFormat::Byte => self.is_byte = true,
977                openapiv3::StringFormat::Binary => self.is_binary = true,
978            },
979            openapiv3::VariantOrUnknownOrEmpty::Unknown(format) => match format.as_str() {
980                "uuid" => self.is_uuid = true,
981                "uri" => self.is_string = true,
982                "date" => self.is_date = true,
983                "date-time" => self.is_date_time = true,
984                _ => {
985                    self.is_string = true;
986                }
987            },
988            openapiv3::VariantOrUnknownOrEmpty::Empty => {
989                self.is_string = true;
990            }
991        }
992
993        if self.is_enum {
994            let enum_vars = by
995                .enumeration
996                .iter()
997                .flatten()
998                .map(|v| EnumValue {
999                    name: v.to_upper_camel_case(),
1000                    value: v.to_string(),
1001                })
1002                .collect::<Vec<_>>();
1003
1004            self.is_model = true;
1005            self.is_string = false;
1006            self.allowable_values.insert("enumVars".into(), enum_vars);
1007            self.data_type.set_enum(&self.name, &self.type_);
1008        } else {
1009            self.is_primitive_type = true;
1010        }
1011
1012        self
1013    }
1014}
1015
1016#[derive(Default, Content, Clone, Debug)]
1017#[ramhorns(rename_all = "camelCase")]
1018pub(crate) struct EnumValue {
1019    name: String,
1020    value: String,
1021}
1022
1023fn floor_log2(x: u64) -> u64 {
1024    65 - (x.leading_zeros() as u64)
1025}