Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions lib/ash_json_api/json_schema/open_api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -948,13 +948,8 @@ if Code.ensure_loaded?(OpenApiSpex) do
type_key = {instance_of, nil, constraints}

if type_key in acc.seen_non_schema_types do
# We're in a recursive loop, return $ref and warn
# We're in a recursive loop, return $ref
# Recursive type detected, using $ref instead of inline definition

Logger.warning(
"Detected recursive embedded type with JSON API type: #{inspect(instance_of)}"
)

schema = %{"$ref" => "#/components/schemas/#{json_api_type}-type"}
{schema, acc}
else
Expand Down
99 changes: 45 additions & 54 deletions test/acceptance/json_schema_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -198,63 +198,54 @@ defmodule Test.Acceptance.JsonSchemaTest do
test "handles self-referential embedded resources in OpenAPI schema without infinite loop" do
# This should complete without timing out
# If it loops infinitely, the test will timeout
import ExUnit.CaptureLog
open_api_spec = AshJsonApi.OpenApi.spec(domain: [Blogs])

log =
capture_log(fn ->
open_api_spec = AshJsonApi.OpenApi.spec(domain: [Blogs])

# Basic assertion to ensure schema was generated
assert %OpenApiSpex.OpenApi{} = open_api_spec
schemas = open_api_spec.components.schemas
assert is_map(schemas)

# Verify that the embedded resource with JSON API type gets its own schema
assert Map.has_key?(schemas, "node-type")
node_schema = schemas["node-type"]
assert node_schema.type == :object

# Verify that tree resource uses $ref to reference the node schema
tree_schema = schemas["tree"]
assert tree_schema.type == :object
tree_attributes = tree_schema.properties.attributes
root_property = tree_attributes.properties.root

# Should be an anyOf with the reference and null (because the attribute allows nil)
assert Map.has_key?(root_property, "anyOf")
any_of = root_property["anyOf"]
assert length(any_of) == 2

# First item should be the node schema (inline)
node_schema =
Enum.find(any_of, fn item ->
match?(%OpenApiSpex.Schema{}, item) && item.type == :object
end)

assert node_schema != nil
assert node_schema.type == :object

# The child property within the node schema should reference the node schema
child_property = node_schema.properties.child
assert Map.has_key?(child_property, "anyOf")
child_any_of = child_property["anyOf"]

# Find the $ref in the child's anyOf
ref_schema = Enum.find(child_any_of, &Map.has_key?(&1, "$ref"))
assert ref_schema["$ref"] == "#/components/schemas/node-type"

# Second item should be null type
null_schema =
Enum.find(any_of, fn item ->
is_map(item) && not match?(%OpenApiSpex.Schema{}, item) && item["type"] == "null"
end)

assert null_schema["type"] == "null"
# Basic assertion to ensure schema was generated
assert %OpenApiSpex.OpenApi{} = open_api_spec
schemas = open_api_spec.components.schemas
assert is_map(schemas)

# Verify that the embedded resource with JSON API type gets its own schema
assert Map.has_key?(schemas, "node-type")
node_schema = schemas["node-type"]
assert node_schema.type == :object

# Verify that tree resource uses $ref to reference the node schema
tree_schema = schemas["tree"]
assert tree_schema.type == :object
tree_attributes = tree_schema.properties.attributes
root_property = tree_attributes.properties.root

# Should be an anyOf with the reference and null (because the attribute allows nil)
assert Map.has_key?(root_property, "anyOf")
any_of = root_property["anyOf"]
assert length(any_of) == 2

# First item should be the node schema (inline)
node_schema =
Enum.find(any_of, fn item ->
match?(%OpenApiSpex.Schema{}, item) && item.type == :object
end)

assert node_schema != nil
assert node_schema.type == :object

# The child property within the node schema should reference the node schema
child_property = node_schema.properties.child
assert Map.has_key?(child_property, "anyOf")
child_any_of = child_property["anyOf"]

# Find the $ref in the child's anyOf
ref_schema = Enum.find(child_any_of, &Map.has_key?(&1, "$ref"))
assert ref_schema["$ref"] == "#/components/schemas/node-type"

# Second item should be null type
null_schema =
Enum.find(any_of, fn item ->
is_map(item) && not match?(%OpenApiSpex.Schema{}, item) && item["type"] == "null"
end)

# Verify that the warning was logged for recursive type
assert log =~
"Detected recursive embedded type with JSON API type: Test.Acceptance.JsonSchemaTest.Node"
assert null_schema["type"] == "null"
end

test "handles recursive embedded inputs for create/update operations without infinite loop" do
Expand Down
Loading