Skip to content

Commit ab689e0

Browse files
committed
fix(crystal): fix partial_oneof_module by using a class instead of a module
Fix #22563
1 parent 89e8f32 commit ab689e0

1 file changed

Lines changed: 70 additions & 101 deletions

File tree

Lines changed: 70 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,104 @@
11
{{#description}}
22
# {{{.}}}
33
{{/description}}
4-
module {{classname}}
4+
class {{classname}}
5+
include JSON::Serializable
6+
include YAML::Serializable
7+
8+
class SchemaMismatchError < Exception
9+
end
10+
511
{{#oneOf}}
612
{{#-first}}
713
# List of class defined in oneOf (OpenAPI v3)
814
def self.openapi_one_of
915
[
1016
{{/-first}}
11-
:"{{{.}}}"{{^-last}},{{/-last}}
17+
{{{.}}}{{^-last}},{{/-last}}
1218
{{#-last}}
1319
]
1420
end
1521

1622
{{/-last}}
1723
{{/oneOf}}
18-
{{#discriminator}}
19-
{{#propertyName}}
20-
# Discriminator's property name (OpenAPI v3)
21-
def self.openapi_discriminator_name
22-
:"{{{.}}}"
23-
end
2424

25-
{{/propertyName}}
26-
{{#mappedModels}}
27-
{{#-first}}
28-
# Discriminator's mapping (OpenAPI v3)
29-
def self.openapi_discriminator_mapping
30-
{
31-
{{/-first}}
32-
:"{{{mappingName}}}" => :"{{{modelName}}}"{{^-last}},{{/-last}}
33-
{{#-last}}
34-
}
35-
end
36-
37-
{{/-last}}
38-
{{/mappedModels}}
39-
{{/discriminator}}
40-
# Builds the object
41-
# @param [Mixed] Data to be matched against the list of oneOf items
42-
# @return [Object] Returns the model or the data itself
43-
def self.build(data)
4425
{{#discriminator}}
45-
discriminator_value = data[openapi_discriminator_name]
46-
return nil unless discriminator_value
47-
{{#mappedModels}}
48-
{{#-first}}
49-
50-
klass = openapi_discriminator_mapping[discriminator_value.to_sym]
51-
return nil unless klass
52-
53-
{{moduleName}}.const_get(klass).build_from_hash(data)
54-
{{/-first}}
55-
{{/mappedModels}}
56-
{{^mappedModels}}
57-
{{moduleName}}.const_get(discriminator_value).build_from_hash(data)
58-
{{/mappedModels}}
26+
use_yaml_discriminator {{#propertyName}}"{{{.}}}"{{/propertyName}}, {
27+
{{#mappedModels}}{{{mappingName}}}: {{{modelName}}}{{^-last}},{{/-last}}{{/mappedModels}}
28+
}
5929
{{/discriminator}}
60-
{{^discriminator}}
61-
# Go through the list of oneOf items and attempt to identify the appropriate one.
62-
# Note:
63-
# - We do not attempt to check whether exactly one item matches.
64-
# - No advanced validation of types in some cases (e.g. "x: { type: string }" will happily match { x: 123 })
65-
# due to the way the deserialization is made in the base_object template (it just casts without verifying).
66-
# - TODO: scalar values are de facto behaving as if they were nullable.
67-
# - TODO: logging when debugging is set.
30+
31+
def self.build(data)
6832
openapi_one_of.each do |klass|
6933
begin
70-
next if klass == :AnyType # "nullable: true"
7134
typed_data = find_and_cast_into_type(klass, data)
7235
return typed_data if typed_data
73-
rescue # rescue all errors so we keep iterating even if the current item lookup raises
36+
rescue ex
37+
# rescue all errors so we keep iterating even if the current item lookup raises
38+
Log.trace { ex.message }
7439
end
7540
end
7641

77-
openapi_one_of.includes?(:AnyType) ? data : nil
78-
{{/discriminator}}
42+
nil
7943
end
80-
{{^discriminator}}
8144

82-
SchemaMismatchError = Class.new(StandardError)
83-
84-
# Note: 'File' is missing here because in the regular case we get the data _after_ a call to JSON.parse.
85-
private def self.find_and_cast_into_type(klass, data)
45+
{{#oneOf}}
46+
private def self.find_and_cast_into_type(klass : {{{.}}}.class, data)
8647
return if data.nil?
8748

88-
begin
89-
case klass.to_s
90-
when "Boolean"
91-
return data if data.instance_of?(TrueClass) || data.instance_of?(FalseClass)
92-
when "Float"
93-
return data if data.instance_of?(Float)
94-
when "Integer"
95-
return data if data.instance_of?(Integer)
96-
when "Time"
97-
return Time.parse(data)
98-
when "Date"
99-
return Date.parse(data)
100-
when "String"
101-
return data if data.instance_of?(String)
102-
when "Object" # "type: object"
103-
return data if data.instance_of?(Hash)
104-
when /\AArray<(?<sub_type>.+)>\z/ # "type: array"
105-
if data.instance_of?(Array)
106-
sub_type = Regexp.last_match[:sub_type]
107-
return data.map { |item| find_and_cast_into_type(sub_type, item) }
108-
end
109-
when /\AHash<String, (?<sub_type>.+)>\z/ # "type: object" with "additionalProperties: { ... }"
110-
if data.instance_of?(Hash) && data.keys.all? { |k| k.instance_of?(Symbol) || k.instance_of?(String) }
111-
sub_type = Regexp.last_match[:sub_type]
112-
return data.each_with_object({} of String | Symbol => Bool | Float | Integer | Time | Date | String | Array | Hash) { |(k, v), hsh| hsh[k] = find_and_cast_into_type(sub_type, v) }
113-
end
114-
else # model
115-
const = {{moduleName}}.const_get(klass)
116-
if const
117-
if const.respond_to?(:openapi_one_of) # nested oneOf model
118-
model = const.build(data)
119-
return model if model
120-
else
121-
# raise if data contains keys that are not known to the model
122-
raise unless (data.keys - const.acceptable_attributes).empty?
123-
model = const.build_from_hash(data)
124-
return model if model && model.valid?
125-
end
126-
end
49+
Log.trace { "INSPECTING DATA" }
50+
Log.trace { data.inspect }
51+
52+
case data
53+
when NetboxClient::RecursiveHash
54+
if (value = cast_value(array_data: false, array_class: array_class?(klass), klass: klass, data: data))
55+
return new(value)
56+
end
57+
when Array(NetboxClient::RecursiveHash)
58+
if (value = cast_value(array_data: true, array_class: array_class?(klass), klass: klass, data: data))
59+
return new(value)
12760
end
61+
else
62+
raise SchemaMismatchError.new("#{data} doesn't match the #{klass} type")
63+
end
64+
end
65+
{{/oneOf}}
66+
67+
private def self.cast_value(array_data : Bool, array_class : Bool, klass, data)
68+
if array_class == true && array_data == true
69+
Log.debug { "Building array of classes: #{klass} / #{data}" }
12870

129-
raise # if no match by now, raise
130-
rescue
131-
raise SchemaMismatchError, "#{data} doesn't match the #{klass} type"
71+
klass.from_json(data.to_json)
72+
elsif array_class == false && array_data == false
73+
Log.debug { "Building single class: #{klass} / #{data}" }
74+
75+
klass.from_json(data.to_json)
76+
end
77+
end
78+
79+
private def self.array_class?(klass)
80+
klass.name.starts_with?("Array(")
81+
end
82+
83+
{{#oneOf}}
84+
def initialize(@value : {{{.}}})
85+
end
86+
87+
{{/oneOf}}
88+
89+
delegate :to_yaml, to: @value
90+
delegate :to_json, to: @value
91+
92+
def to_any_h
93+
{"value" => to_h}
94+
end
95+
96+
def to_h
97+
val = @value
98+
if val.is_a?(Int32)
99+
val
100+
else
101+
val.to_h
132102
end
133103
end
134-
{{/discriminator}}
135104
end

0 commit comments

Comments
 (0)