From c0341588505d9cdd1010d161ab4120ee9cbc24c5 Mon Sep 17 00:00:00 2001 From: Paul Sturgess Date: Tue, 23 Jan 2024 16:30:07 +0000 Subject: [PATCH] feat: ensure apia objects can be declared as nullable In Apia we can declare nullable fields like this: class DiskTemplate < Apia::Object field :description, :string, null: true field :latest_version, Objects::DiskTemplateVersion, null: true end These are now declared as nullable in the spec. closes: https://github.com/krystal/apia-openapi/issues/55 --- examples/core_api/objects/time.rb | 4 +-- lib/apia/open_api/objects/schema.rb | 13 +++++++-- spec/support/fixtures/openapi.json | 43 ++++++++++++++++++++++++----- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/examples/core_api/objects/time.rb b/examples/core_api/objects/time.rb index e049393..dc4d4e8 100644 --- a/examples/core_api/objects/time.rb +++ b/examples/core_api/objects/time.rb @@ -22,7 +22,7 @@ class Time < Apia::Object backend { |t| t.to_s } end - field :year, type: Objects::Year do + field :year, type: Objects::Year, null: true do backend { |t| t.year } end @@ -46,7 +46,7 @@ class Time < Apia::Object backend { |t| Base64.encode64(t.to_s) } end - field :as_date, type: :date do + field :as_date, type: :date, null: true do backend { |t| t.strftime("%Y-%m-%d") } end diff --git a/lib/apia/open_api/objects/schema.rb b/lib/apia/open_api/objects/schema.rb index 4fe1ac4..770be05 100644 --- a/lib/apia/open_api/objects/schema.rb +++ b/lib/apia/open_api/objects/schema.rb @@ -40,7 +40,7 @@ def initialize(spec:, definition:, schema:, id:, endpoint: nil, path: nil) @definition = definition @schema = schema @id = id - @endpoint = endpoint + @endpoint = endpoint # endpoint gets specified when we are dealing with a partial response @path = path @children = [] end @@ -130,6 +130,10 @@ def generate_schema_for_child(schema, child, all_properties_included) schema[:properties][child.name.to_s] = generate_scalar_schema(child) end + if child.try(:null?) + schema[:properties][child.name.to_s][:nullable] = true + end + if child.try(:required?) schema[:required] ||= [] schema[:required] << child.name.to_s @@ -141,7 +145,7 @@ def generate_properties_for_object(schema, child, all_properties_included) schema[:type] = "object" schema[:properties] ||= {} if all_properties_included - schema[:properties][child.name.to_s] = generate_schema_ref(child) + ref = generate_schema_ref(child) else child_path = @path.nil? ? nil : @path + [child] @@ -149,13 +153,16 @@ def generate_properties_for_object(schema, child, all_properties_included) # very long IDs, so we truncate them to avoid hitting the 100 character # filename limit imposed by the rubygems gem builder. truncated_id = @id.match(/^(.*?)\d*?(Response|Part).*$/)[1] - schema[:properties][child.name.to_s] = generate_schema_ref( + ref = generate_schema_ref( child, id: "#{truncated_id}Part_#{child.name}".camelize, endpoint: @endpoint, path: child_path ) end + + # Using allOf is a workaround to allow us to set a ref as `nullable` in OpenAPI 3.0 + schema[:properties][child.name.to_s] = { allOf: [ref] } end end diff --git a/spec/support/fixtures/openapi.json b/spec/support/fixtures/openapi.json index 1de0da1..f63e555 100644 --- a/spec/support/fixtures/openapi.json +++ b/spec/support/fixtures/openapi.json @@ -788,12 +788,21 @@ "type": "integer" }, "year": { - "$ref": "#/components/schemas/PostExampleFormatMultiplePartYear" + "allOf": [ + { + "$ref": "#/components/schemas/PostExampleFormatMultiplePartYear" + } + ], + "nullable": true }, "as_array_of_objects": { "type": "array", "items": { - "$ref": "#/components/schemas/PostExampleFormatMultiplePartAsArrayOfObjects" + "allOf": [ + { + "$ref": "#/components/schemas/PostExampleFormatMultiplePartAsArrayOfObjects" + } + ] } } } @@ -827,7 +836,12 @@ "type": "string" }, "year": { - "$ref": "#/components/schemas/Year" + "allOf": [ + { + "$ref": "#/components/schemas/Year" + } + ], + "nullable": true }, "month": { "$ref": "#/components/schemas/MonthPolymorph" @@ -841,7 +855,11 @@ "as_array_of_objects": { "type": "array", "items": { - "$ref": "#/components/schemas/Year" + "allOf": [ + { + "$ref": "#/components/schemas/Year" + } + ] } }, "as_decimal": { @@ -853,7 +871,8 @@ }, "as_date": { "type": "string", - "format": "date" + "format": "date", + "nullable": true } } }, @@ -961,7 +980,12 @@ "$ref": "#/components/schemas/Day" }, "year": { - "$ref": "#/components/schemas/GetTestObjectPartYear" + "allOf": [ + { + "$ref": "#/components/schemas/GetTestObjectPartYear" + } + ], + "nullable": true } } }, @@ -1121,7 +1145,12 @@ "$ref": "#/components/schemas/Day" }, "year": { - "$ref": "#/components/schemas/PostTestObjectPartYear" + "allOf": [ + { + "$ref": "#/components/schemas/PostTestObjectPartYear" + } + ], + "nullable": true } } },