From 1416792f4ed7070131656ad72c7eaf04d6984afd Mon Sep 17 00:00:00 2001 From: Manfred Stienstra Date: Mon, 23 Feb 2026 19:56:06 +0100 Subject: [PATCH 1/4] Add an example with allOf, anyOf, and oneOf to fixtures. --- test/files/openapi/polymorphic.yml | 105 +++++++++++++++++++++++ test/reynard/schema/model_naming_test.rb | 3 +- 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 test/files/openapi/polymorphic.yml diff --git a/test/files/openapi/polymorphic.yml b/test/files/openapi/polymorphic.yml new file mode 100644 index 0000000..e2727c6 --- /dev/null +++ b/test/files/openapi/polymorphic.yml @@ -0,0 +1,105 @@ +openapi: "3.1.0" +info: + title: Pets + version: 1.0.0 + contact: {} + description: Find out about my pet. +servers: + - url: http://example.com +tags: + - name: pets + description: Animals in custody of a person. +paths: + /pets: + get: + summary: List all pets + description: A list of all my pets. + operationId: getPets + tags: + - pets + responses: + "200": + description: Array of pets + content: + application/json: + schema: + type: array + items: + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + /pets/{id}: + get: + summary: Get pet + description: A description of the pet + operationId: getPet + tags: + - pets + parameters: + - in: path + name: id + required: true + schema: + type: string + responses: + "200": + description: A pet + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + discriminator: + propertyName: pet_type + /status: + get: + summary: Get status of the service + description: A status object for the service + operationId: getStatus + tags: + - pets + responses: + "200": + description: A status + content: + application/json: + schema: + anyOf: + - type: object + properties: + status: + type: string + - type: object + properties: + description: + type: string + - type: object + properties: + status: + type: integer +components: + schemas: + Pet: + type: object + required: + - pet_type + properties: + pet_type: + type: string + discriminator: + propertyName: pet_type + Cat: + allOf: + - $ref: "#/components/schemas/Pet" + - type: object + properties: + red: + type: boolean + Dog: + allOf: + - $ref: "#/components/schemas/Pet" + - type: object + properties: + tial: + type: boolean diff --git a/test/reynard/schema/model_naming_test.rb b/test/reynard/schema/model_naming_test.rb index 44119c8..fb4d20f 100644 --- a/test/reynard/schema/model_naming_test.rb +++ b/test/reynard/schema/model_naming_test.rb @@ -70,7 +70,8 @@ class RegressionModelNamingTest < Reynard::Test 'weird' => %w[ HowdyPardner AFRootWithInThe Fugol Bird Duckbill Duckbill HowdyPardner FugolCollection BirdsCollection DuckbillCollection - ] + ], + 'polymorphic' => %w[Pet PetCollection] }.freeze test 'produces a model name for every schema node in every specification' do From ebc014e2a352da6ee040f20e8cdbb3f60035eaf4 Mon Sep 17 00:00:00 2001 From: Manfred Stienstra Date: Mon, 23 Feb 2026 20:08:03 +0100 Subject: [PATCH 2/4] Add method to determine the schema type. --- lib/reynard/schema.rb | 8 ++++++++ test/reynard/schema_test.rb | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/lib/reynard/schema.rb b/lib/reynard/schema.rb index 8a6f6d3..c111e7a 100644 --- a/lib/reynard/schema.rb +++ b/lib/reynard/schema.rb @@ -46,6 +46,14 @@ def property_schema(name) ) end + def self.determine_schema_type(schema) + if schema.key?('type') + schema['type'] + elsif schema.keys.intersect?(%w[allOf anyOf oneOf]) + 'object' + end + end + private def model_naming diff --git a/test/reynard/schema_test.rb b/test/reynard/schema_test.rb index 7605929..733605c 100644 --- a/test/reynard/schema_test.rb +++ b/test/reynard/schema_test.rb @@ -4,6 +4,29 @@ class Reynard class SchemaTest < Reynard::Test + test 'uses object schema type for object schemas' do + assert_equal('object', Schema.determine_schema_type({ 'type' => 'object' })) + end + + test 'uses array schema type for array schemas' do + assert_equal('array', Schema.determine_schema_type({ 'type' => 'array' })) + end + + test 'uses object schema type for allOf' do + assert_equal('object', Schema.determine_schema_type({ 'allOf' => {} })) + end + + test 'uses object schema type for anyOf' do + assert_equal('object', Schema.determine_schema_type({ 'anyOf' => {} })) + end + + test 'uses object schema type for oneOf' do + assert_equal('object', Schema.determine_schema_type({ 'oneOf' => {} })) + end + + test 'does not return a schema type for unsupported schemas' do + assert_nil Schema.determine_schema_type({}) + end end class SingularTopLevelSchemaTest < Reynard::Test From 789e03d7d607329bd2d33e002de86878f0331175 Mon Sep 17 00:00:00 2001 From: Manfred Stienstra Date: Mon, 23 Feb 2026 20:10:03 +0100 Subject: [PATCH 3/4] Initialize model for polymorphic schemas. --- lib/reynard/schema.rb | 3 +- test/reynard/object_builder_test.rb | 50 +++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/lib/reynard/schema.rb b/lib/reynard/schema.rb index c111e7a..149085a 100644 --- a/lib/reynard/schema.rb +++ b/lib/reynard/schema.rb @@ -16,7 +16,8 @@ def initialize(specification:, node:, namespace: nil) def type return @type if defined?(@type) - @type = @specification.dig(*node, 'type') + schema = @specification.dig(*node) + @type = schema ? self.class.determine_schema_type(schema) : nil end def model_name diff --git a/test/reynard/object_builder_test.rb b/test/reynard/object_builder_test.rb index 3626d94..e79a6d6 100644 --- a/test/reynard/object_builder_test.rb +++ b/test/reynard/object_builder_test.rb @@ -305,4 +305,54 @@ def setup assert_model_name('NationalIndustry', national_industry) end end + + class PolymorphicBuilderTest < Reynard::Test + Response = Struct.new(:body, keyword_init: true) + + def setup + @specification = Specification.new(filename: fixture_file('openapi/polymorphic.yml')) + @inflector = Inflector.new + end + + test 'handles oneOf and allOf' do + operation = @specification.operation('getPets') + media_type = @specification.media_type(operation.node, '200', 'application/json') + schema = @specification.schema(media_type.node) + collection = Reynard::ObjectBuilder.new( + schema:, + inflector: @inflector, + parsed_body: [ + { + 'pet_type' => 'Dog', + 'tail' => true + } + ] + ).call + + assert_equal(1, collection.size) + record = collection[0] + + assert_model_name('Pet', record) + assert_equal 'Dog', record.pet_type + assert record.tail + end + + test 'handles anyOf' do + operation = @specification.operation('getStatus') + media_type = @specification.media_type(operation.node, '200', 'application/json') + schema = @specification.schema(media_type.node) + record = Reynard::ObjectBuilder.new( + schema:, + inflector: @inflector, + parsed_body: { + 'status' => 'online', + 'description' => 'Everything works' + } + ).call + + assert_model_name('Statu', record) + assert_equal 'online', record.status + assert_equal 'Everything works', record.description + end + end end From f78bc253abb6fadeb4dfda98d78c121e2747402a Mon Sep 17 00:00:00 2001 From: Manfred Stienstra Date: Mon, 23 Feb 2026 20:16:13 +0100 Subject: [PATCH 4/4] Add whitespace for new RuboCop rule. --- lib/reynard.rb | 1 + lib/reynard/context.rb | 1 + lib/reynard/http/response.rb | 1 + lib/reynard/model.rb | 1 + 4 files changed, 4 insertions(+) diff --git a/lib/reynard.rb b/lib/reynard.rb index c56695f..c138a5a 100644 --- a/lib/reynard.rb +++ b/lib/reynard.rb @@ -11,6 +11,7 @@ # OpenAPI specification. class Reynard extend Forwardable + def_delegators :build_context, :logger, :base_url, :operation, :headers, :params def_delegators :@specification, :servers def_delegators :@inflector, :snake_cases diff --git a/lib/reynard/context.rb b/lib/reynard/context.rb index 7e532b5..5e680bb 100644 --- a/lib/reynard/context.rb +++ b/lib/reynard/context.rb @@ -4,6 +4,7 @@ class Reynard # Exposes a public interface to build a request context. class Context extend Forwardable + def_delegators :@request_context, :verb, :path, :full_path, :url def initialize(specification:, inflector:, request_context: nil) diff --git a/lib/reynard/http/response.rb b/lib/reynard/http/response.rb index 0e63b4b..8628ff9 100644 --- a/lib/reynard/http/response.rb +++ b/lib/reynard/http/response.rb @@ -6,6 +6,7 @@ class Http # in the specification. class Response extend Forwardable + def_delegators :@http_response, :code, :content_type, :[], :body def initialize(specification:, inflector:, request_context:, http_response:) diff --git a/lib/reynard/model.rb b/lib/reynard/model.rb index 5c9f35b..93b3367 100644 --- a/lib/reynard/model.rb +++ b/lib/reynard/model.rb @@ -4,6 +4,7 @@ class Reynard # Superclass for dynamic classes generated by the object builder. class Model < BasicObject extend ::Forwardable + def_delegators :@attributes, :[], :empty? class << self