Skip to content

Commit b856a9e

Browse files
committed
fix(crystal): various fixes for partial_oneof_module.mustache Crystal template
1. `class << self` doesn't exist in Crystal, you must prefix class methods with `self.` 2. use double quotes for String litterals (simple quotes are for Char litterals) 3. global `private` keyword doesn't exist in Crystal, you must prefix private methods with `private` 4. you must specify types when using `each_with_object({})`
1 parent 338c999 commit b856a9e

1 file changed

Lines changed: 113 additions & 117 deletions

File tree

modules/openapi-generator/src/main/resources/crystal/partial_oneof_module.mustache

Lines changed: 113 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -2,136 +2,132 @@
22
# {{{.}}}
33
{{/description}}
44
module {{classname}}
5-
class << self
6-
{{#oneOf}}
7-
{{#-first}}
8-
# List of class defined in oneOf (OpenAPI v3)
9-
def openapi_one_of
10-
[
11-
{{/-first}}
12-
:'{{{.}}}'{{^-last}},{{/-last}}
13-
{{#-last}}
14-
]
15-
end
5+
{{#oneOf}}
6+
{{#-first}}
7+
# List of class defined in oneOf (OpenAPI v3)
8+
def self.openapi_one_of
9+
[
10+
{{/-first}}
11+
:"{{{.}}}"{{^-last}},{{/-last}}
12+
{{#-last}}
13+
]
14+
end
1615

17-
{{/-last}}
18-
{{/oneOf}}
19-
{{#discriminator}}
20-
{{#propertyName}}
21-
# Discriminator's property name (OpenAPI v3)
22-
def openapi_discriminator_name
23-
:'{{{.}}}'
24-
end
16+
{{/-last}}
17+
{{/oneOf}}
18+
{{#discriminator}}
19+
{{#propertyName}}
20+
# Discriminator's property name (OpenAPI v3)
21+
def self.openapi_discriminator_name
22+
:"{{{.}}}"
23+
end
2524

26-
{{/propertyName}}
27-
{{#mappedModels}}
28-
{{#-first}}
29-
# Discriminator's mapping (OpenAPI v3)
30-
def openapi_discriminator_mapping
31-
{
32-
{{/-first}}
33-
:'{{{mappingName}}}' => :'{{{modelName}}}'{{^-last}},{{/-last}}
34-
{{#-last}}
35-
}
36-
end
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
3736

38-
{{/-last}}
39-
{{/mappedModels}}
40-
{{/discriminator}}
41-
# Builds the object
42-
# @param [Mixed] Data to be matched against the list of oneOf items
43-
# @return [Object] Returns the model or the data itself
44-
def build(data)
45-
{{#discriminator}}
46-
discriminator_value = data[openapi_discriminator_name]
47-
return nil unless discriminator_value
48-
{{#mappedModels}}
49-
{{#-first}}
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)
44+
{{#discriminator}}
45+
discriminator_value = data[openapi_discriminator_name]
46+
return nil unless discriminator_value
47+
{{#mappedModels}}
48+
{{#-first}}
5049

51-
klass = openapi_discriminator_mapping[discriminator_value.to_sym]
52-
return nil unless klass
50+
klass = openapi_discriminator_mapping[discriminator_value.to_sym]
51+
return nil unless klass
5352

54-
{{moduleName}}.const_get(klass).build_from_hash(data)
55-
{{/-first}}
56-
{{/mappedModels}}
57-
{{^mappedModels}}
58-
{{moduleName}}.const_get(discriminator_value).build_from_hash(data)
59-
{{/mappedModels}}
60-
{{/discriminator}}
61-
{{^discriminator}}
62-
# Go through the list of oneOf items and attempt to identify the appropriate one.
63-
# Note:
64-
# - We do not attempt to check whether exactly one item matches.
65-
# - No advanced validation of types in some cases (e.g. "x: { type: string }" will happily match { x: 123 })
66-
# due to the way the deserialization is made in the base_object template (it just casts without verifying).
67-
# - TODO: scalar values are de facto behaving as if they were nullable.
68-
# - TODO: logging when debugging is set.
69-
openapi_one_of.each do |klass|
70-
begin
71-
next if klass == :AnyType # "nullable: true"
72-
typed_data = find_and_cast_into_type(klass, data)
73-
return typed_data if typed_data
74-
rescue # rescue all errors so we keep iterating even if the current item lookup raises
75-
end
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}}
59+
{{/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.
68+
openapi_one_of.each do |klass|
69+
begin
70+
next if klass == :AnyType # "nullable: true"
71+
typed_data = find_and_cast_into_type(klass, data)
72+
return typed_data if typed_data
73+
rescue # rescue all errors so we keep iterating even if the current item lookup raises
7674
end
77-
78-
openapi_one_of.includes?(:AnyType) ? data : nil
79-
{{/discriminator}}
8075
end
81-
{{^discriminator}}
8276

83-
private
77+
openapi_one_of.includes?(:AnyType) ? data : nil
78+
{{/discriminator}}
79+
end
80+
{{^discriminator}}
8481

85-
SchemaMismatchError = Class.new(StandardError)
82+
SchemaMismatchError = Class.new(StandardError)
8683

87-
# Note: 'File' is missing here because in the regular case we get the data _after_ a call to JSON.parse.
88-
def find_and_cast_into_type(klass, data)
89-
return if data.nil?
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)
86+
return if data.nil?
9087

91-
case klass.to_s
92-
when 'Boolean'
93-
return data if data.instance_of?(TrueClass) || data.instance_of?(FalseClass)
94-
when 'Float'
95-
return data if data.instance_of?(Float)
96-
when 'Integer'
97-
return data if data.instance_of?(Integer)
98-
when 'Time'
99-
return Time.parse(data)
100-
when 'Date'
101-
return Date.parse(data)
102-
when 'String'
103-
return data if data.instance_of?(String)
104-
when 'Object' # "type: object"
105-
return data if data.instance_of?(Hash)
106-
when /\AArray<(?<sub_type>.+)>\z/ # "type: array"
107-
if data.instance_of?(Array)
108-
sub_type = Regexp.last_match[:sub_type]
109-
return data.map { |item| find_and_cast_into_type(sub_type, item) }
110-
end
111-
when /\AHash<String, (?<sub_type>.+)>\z/ # "type: object" with "additionalProperties: { ... }"
112-
if data.instance_of?(Hash) && data.keys.all? { |k| k.instance_of?(Symbol) || k.instance_of?(String) }
113-
sub_type = Regexp.last_match[:sub_type]
114-
return data.each_with_object({}) { |(k, v), hsh| hsh[k] = find_and_cast_into_type(sub_type, v) }
115-
end
116-
else # model
117-
const = {{moduleName}}.const_get(klass)
118-
if const
119-
if const.respond_to?(:openapi_one_of) # nested oneOf model
120-
model = const.build(data)
121-
return model if model
122-
else
123-
# raise if data contains keys that are not known to the model
124-
raise unless (data.keys - const.acceptable_attributes).empty?
125-
model = const.build_from_hash(data)
126-
return model if model && model.valid?
127-
end
88+
case klass.to_s
89+
when "Boolean"
90+
return data if data.instance_of?(TrueClass) || data.instance_of?(FalseClass)
91+
when "Float"
92+
return data if data.instance_of?(Float)
93+
when "Integer"
94+
return data if data.instance_of?(Integer)
95+
when "Time"
96+
return Time.parse(data)
97+
when "Date"
98+
return Date.parse(data)
99+
when "String"
100+
return data if data.instance_of?(String)
101+
when "Object" # "type: object"
102+
return data if data.instance_of?(Hash)
103+
when /\AArray<(?<sub_type>.+)>\z/ # "type: array"
104+
if data.instance_of?(Array)
105+
sub_type = Regexp.last_match[:sub_type]
106+
return data.map { |item| find_and_cast_into_type(sub_type, item) }
107+
end
108+
when /\AHash<String, (?<sub_type>.+)>\z/ # "type: object" with "additionalProperties: { ... }"
109+
if data.instance_of?(Hash) && data.keys.all? { |k| k.instance_of?(Symbol) || k.instance_of?(String) }
110+
sub_type = Regexp.last_match[:sub_type]
111+
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) }
112+
end
113+
else # model
114+
const = {{moduleName}}.const_get(klass)
115+
if const
116+
if const.respond_to?(:openapi_one_of) # nested oneOf model
117+
model = const.build(data)
118+
return model if model
119+
else
120+
# raise if data contains keys that are not known to the model
121+
raise unless (data.keys - const.acceptable_attributes).empty?
122+
model = const.build_from_hash(data)
123+
return model if model && model.valid?
128124
end
129125
end
130-
131-
raise # if no match by now, raise
132-
rescue
133-
raise SchemaMismatchError, "#{data} doesn't match the #{klass} type"
134126
end
135-
{{/discriminator}}
127+
128+
raise # if no match by now, raise
129+
rescue
130+
raise SchemaMismatchError, "#{data} doesn't match the #{klass} type"
136131
end
132+
{{/discriminator}}
137133
end

0 commit comments

Comments
 (0)