Skip to content

Commit fa56cdd

Browse files
fix(specs): more accurate composition behavior typing (generated)
algolia/api-clients-automation#5892 Co-authored-by: algolia-bot <accounts+algolia-api-client-bot@algolia.com> Co-authored-by: Gavin Wade <gavin.wade12@gmail.com>
1 parent 24e3a6f commit fa56cdd

File tree

3 files changed

+501
-190
lines changed

3 files changed

+501
-190
lines changed

lib/algolia/models/composition/composition_behavior.rb

Lines changed: 83 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -8,207 +8,100 @@
88
module Algolia
99
module Composition
1010
# An object containing either an `injection` or `multifeed` behavior schema, but not both.
11-
class CompositionBehavior
12-
attr_accessor :injection
13-
14-
attr_accessor :multifeed
15-
16-
# Attribute mapping from ruby-style variable name to JSON key.
17-
def self.attribute_map
18-
{
19-
:injection => :injection,
20-
:multifeed => :multifeed
21-
}
22-
end
23-
24-
# Attribute type mapping.
25-
def self.types_mapping
26-
{
27-
:injection => :"Injection",
28-
:multifeed => :"Multifeed"
29-
}
30-
end
31-
32-
# List of attributes with nullable: true
33-
def self.openapi_nullable
34-
Set.new(
35-
[]
36-
)
37-
end
38-
39-
# Initializes the object
40-
# @param [Hash] attributes Model attributes in the form of hash
41-
def initialize(attributes = {})
42-
if (!attributes.is_a?(Hash))
43-
raise(
44-
ArgumentError,
45-
"The input argument (attributes) must be a hash in `Algolia::CompositionBehavior` initialize method"
46-
)
11+
module CompositionBehavior
12+
class << self
13+
# List of class defined in oneOf (OpenAPI v3)
14+
def openapi_one_of
15+
[
16+
:"CompositionInjectionBehavior",
17+
:"CompositionMultifeedBehavior"
18+
]
4719
end
4820

49-
# check to see if the attribute exists and convert string to symbol for hash key
50-
attributes = attributes.each_with_object({}) { |(k, v), h|
51-
if (!self.class.attribute_map.key?(k.to_sym))
52-
raise(
53-
ArgumentError,
54-
"`#{k}` is not a valid attribute in `Algolia::CompositionBehavior`. Please check the name to make sure it's valid. List of attributes: " +
55-
self.class.attribute_map.keys.inspect
56-
)
21+
# Builds the object
22+
# @param [Mixed] Data to be matched against the list of oneOf items
23+
# @return [Object] Returns the model or the data itself
24+
def build(data)
25+
# Go through the list of oneOf items and attempt to identify the appropriate one.
26+
# Note:
27+
# - We do not attempt to check whether exactly one item matches.
28+
# - No advanced validation of types in some cases (e.g. "x: { type: string }" will happily match { x: 123 })
29+
# due to the way the deserialization is made in the base_object template (it just casts without verifying).
30+
# - TODO: scalar values are de facto behaving as if they were nullable.
31+
# - TODO: logging when debugging is set.
32+
openapi_one_of.each do |klass|
33+
begin
34+
# "nullable: true"
35+
next if klass == :AnyType
36+
typed_data = find_and_cast_into_type(klass, data)
37+
return typed_data if typed_data
38+
# rescue all errors so we keep iterating even if the current item lookup raises
39+
rescue
40+
end
5741
end
5842

59-
h[k.to_sym] = v
60-
}
61-
62-
if attributes.key?(:injection)
63-
self.injection = attributes[:injection]
43+
openapi_one_of.include?(:AnyType) ? data : nil
6444
end
6545

66-
if attributes.key?(:multifeed)
67-
self.multifeed = attributes[:multifeed]
68-
end
69-
end
70-
71-
# Checks equality by comparing each attribute.
72-
# @param [Object] Object to be compared
73-
def ==(other)
74-
return true if self.equal?(other)
75-
self.class == other.class &&
76-
injection == other.injection &&
77-
multifeed == other.multifeed
78-
end
79-
80-
# @see the `==` method
81-
# @param [Object] Object to be compared
82-
def eql?(other)
83-
self == other
84-
end
85-
86-
# Calculates hash code according to all attributes.
87-
# @return [Integer] Hash code
88-
def hash
89-
[injection, multifeed].hash
90-
end
91-
92-
# Builds the object from hash
93-
# @param [Hash] attributes Model attributes in the form of hash
94-
# @return [Object] Returns the model itself
95-
def self.build_from_hash(attributes)
96-
return nil unless attributes.is_a?(Hash)
97-
attributes = attributes.transform_keys(&:to_sym)
98-
transformed_hash = {}
99-
types_mapping.each_pair do |key, type|
100-
if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
101-
transformed_hash[key.to_sym] = nil
102-
elsif type =~ /\AArray<(.*)>/i
103-
# check to ensure the input is an array given that the attribute
104-
# is documented as an array but the input is not
105-
if attributes[attribute_map[key]].is_a?(Array)
106-
transformed_hash[key.to_sym] = attributes[attribute_map[key]].map { |v|
107-
_deserialize(::Regexp.last_match(1), v)
108-
}
46+
private
47+
48+
SchemaMismatchError = Class.new(StandardError)
49+
50+
def find_and_cast_into_type(klass, data)
51+
return if data.nil?
52+
53+
case klass.to_s
54+
when "Boolean"
55+
return data if data.instance_of?(TrueClass) || data.instance_of?(FalseClass)
56+
when "Float"
57+
return data if data.instance_of?(Float)
58+
when "Integer"
59+
return data if data.instance_of?(Integer)
60+
when "Time"
61+
return Time.parse(data)
62+
when "Date"
63+
return Date.parse(data)
64+
when "String"
65+
return data if data.instance_of?(String)
66+
# "type: object"
67+
when "Object"
68+
return data if data.instance_of?(Hash)
69+
# "type: array"
70+
when /\AArray<(?<sub_type>.+)>\z/
71+
if data.instance_of?(Array)
72+
sub_type = Regexp.last_match[:sub_type]
73+
return data.map { |item| find_and_cast_into_type(sub_type, item) }
10974
end
110-
elsif !attributes[attribute_map[key]].nil?
111-
transformed_hash[key.to_sym] = _deserialize(type, attributes[attribute_map[key]])
112-
end
113-
end
114-
115-
new(transformed_hash)
116-
end
117-
118-
# Deserializes the data based on type
119-
# @param string type Data type
120-
# @param string value Value to be deserialized
121-
# @return [Object] Deserialized data
122-
def self._deserialize(type, value)
123-
case type.to_sym
124-
when :Time
125-
Time.parse(value)
126-
when :Date
127-
Date.parse(value)
128-
when :String
129-
value.to_s
130-
when :Integer
131-
value.to_i
132-
when :Float
133-
value.to_f
134-
when :Boolean
135-
if value.to_s =~ /\A(true|t|yes|y|1)\z/i
136-
true
75+
# "type: object" with "additionalProperties: { ... }"
76+
when /\AHash<String, (?<sub_type>.+)>\z/
77+
if data.instance_of?(Hash) && data.keys.all? { |k| k.instance_of?(Symbol) || k.instance_of?(String) }
78+
sub_type = Regexp.last_match[:sub_type]
79+
return data.each_with_object({}) { |(k, v), hsh| hsh[k] = find_and_cast_into_type(sub_type, v) }
80+
end
81+
# model
13782
else
138-
false
139-
end
140-
141-
when :Object
142-
# generic object (usually a Hash), return directly
143-
value
144-
when /\AArray<(?<inner_type>.+)>\z/
145-
inner_type = Regexp.last_match[:inner_type]
146-
value.map { |v| _deserialize(inner_type, v) }
147-
when /\AHash<(?<k_type>.+?), (?<v_type>.+)>\z/
148-
k_type = Regexp.last_match[:k_type]
149-
v_type = Regexp.last_match[:v_type]
150-
{}.tap do |hash|
151-
value.each do |k, v|
152-
hash[_deserialize(k_type, k)] = _deserialize(v_type, v)
83+
const = Algolia::Composition.const_get(klass)
84+
if const
85+
if const.respond_to?(:openapi_one_of)
86+
# nested oneOf model
87+
model = const.build(data)
88+
elsif const.respond_to?(:discriminator_attributes)
89+
if const.discriminator_attributes.all? { |attr| data.key?(attr) }
90+
model = const.build_from_hash(data)
91+
end
92+
else
93+
# maybe it's an enum, or doens't have discriminators
94+
model = const.build_from_hash(data)
95+
end
96+
97+
return model if model
15398
end
15499
end
155-
# model
156-
else
157-
# models (e.g. Pet) or oneOf
158-
klass = Algolia::Composition.const_get(type)
159-
klass.respond_to?(:openapi_any_of) || klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass
160-
.build_from_hash(value)
161-
end
162-
end
163100

164-
# Returns the string representation of the object
165-
# @return [String] String presentation of the object
166-
def to_s
167-
to_hash.to_s
168-
end
169-
170-
# to_body is an alias to to_hash (backward compatibility)
171-
# @return [Hash] Returns the object in the form of hash
172-
def to_body
173-
to_hash
174-
end
175-
176-
def to_json(*_args)
177-
to_hash.to_json
178-
end
179-
180-
# Returns the object in the form of hash
181-
# @return [Hash] Returns the object in the form of hash
182-
def to_hash
183-
hash = {}
184-
self.class.attribute_map.each_pair do |attr, param|
185-
value = send(attr)
186-
if value.nil?
187-
is_nullable = self.class.openapi_nullable.include?(attr)
188-
next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
189-
end
190-
191-
hash[param] = _to_hash(value)
192-
end
193-
194-
hash
195-
end
196-
197-
# Outputs non-array value in the form of hash
198-
# For object, use to_hash. Otherwise, just return the value
199-
# @param [Object] value Any valid value
200-
# @return [Hash] Returns the value in the form of hash
201-
def _to_hash(value)
202-
if value.is_a?(Array)
203-
value.compact.map { |v| _to_hash(v) }
204-
elsif value.is_a?(Hash)
205-
{}.tap do |hash|
206-
value.each { |k, v| hash[k] = _to_hash(v) }
207-
end
208-
elsif value.respond_to?(:to_hash)
209-
value.to_hash
210-
else
211-
value
101+
# if no match by now, raise
102+
raise
103+
rescue
104+
raise SchemaMismatchError, "#{data} doesn't match the #{klass} type"
212105
end
213106
end
214107
end

0 commit comments

Comments
 (0)