diff --git a/lib/active_model/entity/schemas/json.rb b/lib/active_model/entity/schemas/json.rb index fb246ce..c6dc7cf 100644 --- a/lib/active_model/entity/schemas/json.rb +++ b/lib/active_model/entity/schemas/json.rb @@ -44,13 +44,29 @@ def enum_attributes end end - def json_schema_attribute_for(type) + def primitive_type_schema(type) return { type: :object } if type.type.nil? return { type: :number } if NUMBER_TYPES.include?(type.type) return { type: :string } if STRING_TYPES.include?(type.type) return { type: :boolean } if BOOLEAN_TYPES.include?(type.type) - return { "$ref": type.entity_type.json_schema_ref } if type.is_a?(Type::Entity) - return { items: json_schema_attribute_for(type.element_type), type: :array } if type.is_a?(Type::Array) + + nil + end + + def entity_schema_for(type, inline) + inline ? type.entity_type.as_json_schema(inline:) : { "$ref": type.entity_type.json_schema_ref } + end + + def array_schema_for(type, inline) + { items: json_schema_attribute_for(type.element_type, inline:), type: :array } + end + + def json_schema_attribute_for(type, inline: false) + schema = primitive_type_schema(type) + return schema if schema + + return entity_schema_for(type, inline) if type.is_a?(Type::Entity) + return array_schema_for(type, inline) if type.is_a?(Type::Array) raise NotImplementedError end @@ -68,14 +84,14 @@ def append_enum!(values, options) options[:enum] = values end - def as_json_schema + def as_json_schema(inline: false) type = :object description = meta_descriptions[nil].first required = required_attributes.map(&:name).map { _1.camelize(:lower) } nullable = nullable_attributes.map(&:name).index_by { _1.camelize(:lower) } attributes = attribute_types.transform_keys { _1.camelize(:lower) } - properties = attributes.transform_values { json_schema_attribute_for(_1) } + properties = attributes.transform_values { json_schema_attribute_for(_1, inline:) } enums = enum_attributes.transform_keys { _1.camelize(:lower) } properties.each do |name, options| diff --git a/spec/active_model/entity/schemas/json_spec.rb b/spec/active_model/entity/schemas/json_spec.rb index 56f9499..b037e98 100644 --- a/spec/active_model/entity/schemas/json_spec.rb +++ b/spec/active_model/entity/schemas/json_spec.rb @@ -70,4 +70,57 @@ class Person properties: }) end + + context "with inline: true" do + it "inlines nested entity schemas" do + schema = SchemasTest::Person.as_json_schema(inline: true) + + expect(schema[:properties]["fieldRole"]).to eq({ + type: :object, + required: [], + properties: { "fieldName" => { type: :string } } + }) + end + + it "inlines array of entity schemas" do + schema = SchemasTest::Person.as_json_schema(inline: true) + + expect(schema[:properties]["fieldRoles"]).to eq({ + type: :array, + items: { + type: :object, + required: [], + properties: { "fieldName" => { type: :string } } + } + }) + end + + it "preserves nullable flag on inlined entities" do + schema = SchemasTest::Person.as_json_schema(inline: true) + + expect(schema[:properties]["fieldNullableRole"]).to eq({ + type: :object, + required: [], + properties: { "fieldName" => { type: :string } }, + nullable: true + }) + end + + it "keeps primitive types unchanged" do + schema = SchemasTest::Person.as_json_schema(inline: true) + + expect(schema[:properties]["fieldString"]).to eq({ type: :string }) + expect(schema[:properties]["fieldInteger"]).to eq({ type: :number }) + expect(schema[:properties]["fieldBoolean"]).to eq({ type: :boolean }) + end + + it "keeps array of primitives unchanged" do + schema = SchemasTest::Person.as_json_schema(inline: true) + + expect(schema[:properties]["fieldIntegers"]).to eq({ + type: :array, + items: { type: :number } + }) + end + end end