Created by: jfeltesse-mdsol
Fixes #3630 (closed).
I have yet to update the documentation related templates and update the samples but opening now to gather initial feedback on the direction.
Basically, this builds up on the work from @bkabrda on the java and go experimental clients.
I didn't go "ruby-experiemental" because the support of oneOf in the current ruby client is broken anyway. not doing this in the end.
How it works is, in the spirit of the "oneof as interfaces" this creates a module, not a class, for oneOf models and provides a set of class methods related to the functionality.
The core method is build_from_hash
whose job in this case is to find the relevant oneOf item and transparently building and returning the correct model.
It does so by using the discriminator if present, mapping if present and "falls back" to the existing validation-based look up.
I changed some test yaml files to include more oneOf examples. no need in the end.
If I try to do a before/after using the modified composed-one.yaml
file, it goes like (some parts trimmed for brevity):
openapi-generator generate -i composed-oneof.yaml -g ruby --library faraday -o ./out/ruby_oneof
model files
# current master
custom_one_of_schema.rb
obj_a.rb
obj_b.rb
obj_c.rb
obj_d.rb
# this branch
custom_one_of_array_schema_one_of.rb
custom_one_of_schema.rb
obj_a.rb
obj_b.rb
obj_c.rb
obj_c_data_one_of.rb
obj_d.rb
custom_one_of_schema.rb
# current master
module OpenapiClient
class CustomOneOfSchema
attr_accessor :realtype
attr_accessor :message
attr_accessor :description
attr_accessor :code
# Attribute mapping from ruby-style variable name to JSON key.
def self.attribute_map
{
:'realtype' => :'realtype',
:'message' => :'message',
:'description' => :'description',
:'code' => :'code'
}
end
# Attribute type mapping.
def self.openapi_types
{
:'realtype' => :'String',
:'message' => :'String',
:'description' => :'String',
:'code' => :'Integer'
}
end
# List of attributes with nullable: true
def self.openapi_nullable
Set.new([
])
end
# List of class defined in oneOf (OpenAPI v3)
def self.openapi_one_of
[
:'ObjA',
:'ObjB'
]
end
# discriminator's property name in OpenAPI v3
def self.openapi_discriminator_name
:'realtype'
end
# Initializes the object
# @param [Hash] attributes Model attributes in the form of hash
def initialize(attributes = {})
# another 200 lines of model code...
# this branch
module OpenapiClient
module CustomOneOfSchema
# List of class defined in oneOf (OpenAPI v3)
def self.openapi_one_of
[
:'ObjA',
:'ObjB'
]
end
# discriminator's property name in OpenAPI v3
def self.openapi_discriminator_name
:'realtype'
end
# discriminator's mapping in OpenAPI v3
def self.openapi_discriminator_mapping
{
:'a-type' => :'ObjA',
:'b-type' => :'ObjB'
}
end
# Builds the object from hash
# @param [Hash] attributes Model attributes in the form of hash
# @return [Object] Returns the model itself
def self.build_from_hash(attributes)
discriminator_value = attributes[openapi_discriminator_name]
return nil unless discriminator_value
_class = openapi_discriminator_mapping[discriminator_value.to_sym]
return nil unless _class
OpenapiClient.const_get(_class).build_from_hash(attributes)
end
end
end
Noteworthy is in the obj_c.rb the nested oneOf property has its model generated, albeit with a sligthly different name:
# current master
def self.openapi_types
{
:'realtype' => :'String',
:'data' => :'OneOfObjAObjB' # <==== model not generated!
}
end
# this branch
def self.openapi_types
{
:'realtype' => :'String',
:'data' => :'ObjCDataOneOf' # <==== "interface" module generated
}
end
Limitations / bugs / food for thought
- Interestingly, a
custom_one_of_array_schema_one_of.rb
"interface" module is now generated but the oneOf is taking place inside theitems
. I'm not sure this solution is going to work here. In this case maybe a regular model should be generated alongside somecustom_one_of_array_schema_one_of_items.rb
"interface" module? @bkabrda how do the java and experimental go clients fare in this case? - In java the interface pattern used in this PR makes the models referenced in a oneOf carry the "dynamic" props illustrated in ObjD. https://github.com/OpenAPITools/openapi-generator/pull/4695 sort of discourages that pattern but I guess if the tool can do it, why not. What could be improved in the case of ruby though is to make some "dynamicProps" module in the interface module and dynamically include the props at runtime upon deserializing. Performance may not be stellar so it's trade off between "pureness" of the models and speed I guess.
PR checklist
-
Read the contribution guidelines. -
If contributing template-only or documentation-only changes which will change sample output, build the project before. -
Run the shell script(s) under ./bin/
(or Windows batch scripts under.\bin\windows
) to update Petstore samples related to your fix. This is important, as CI jobs will verify all generator outputs of your HEAD commit, and these must match the expectations made by your contribution. You only need to run./bin/{LANG}-petstore.sh
,./bin/openapi3/{LANG}-petstore.sh
if updating the code or mustache templates for a language ({LANG}
) (e.g. php, ruby, python, etc). -
File the PR against the correct branch: master
,4.3.x
,5.0.x
. Default:master
. -
Copy the technical committee to review the pull request if your PR is targeting a particular programming language.
@cliffano (2017/07) @zlx (2017/09) @autopp (2019/02)