Skip to content
GitLab
Projects Groups Snippets
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in / Register
  • O openapi-generator
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 3,476
    • Issues 3,476
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 402
    • Merge requests 402
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Packages and registries
    • Packages and registries
    • Package Registry
    • Infrastructure Registry
  • Monitor
    • Monitor
    • Incidents
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • OpenAPI Tools
  • openapi-generator
  • Merge requests
  • !5706

[ruby] fix oneOf handling

  • Review changes

  • Download
  • Email patches
  • Plain diff
Merged Administrator requested to merge github/fork/jfeltesse-mdsol/ruby/fix_oneof into master Mar 25, 2020
  • Overview 0
  • Commits 4
  • Pipelines 0
  • Changes 221

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 the items. I'm not sure this solution is going to work here. In this case maybe a regular model should be generated alongside some custom_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)

Assignee
Assign to
Reviewers
Request review from
Time tracking
Source branch: github/fork/jfeltesse-mdsol/ruby/fix_oneof