@@ -256,5 +256,126 @@ defmodule Test.Acceptance.JsonSchemaTest do
256
256
assert log =~
257
257
"Detected recursive embedded type with JSON API type: Test.Acceptance.JsonSchemaTest.Node"
258
258
end
259
+
260
+ test "handles recursive embedded inputs for create/update operations without infinite loop" do
261
+ # This test ensures that embedded resources with self-references in input schemas
262
+ # (for create/update operations) properly use $ref references and don't cause stack overflow
263
+ import ExUnit.CaptureLog
264
+
265
+ # Create a standalone test module for this specific test
266
+ defmodule RecursiveInputTest do
267
+ defmodule RecursiveComment do
268
+ use Ash.Resource ,
269
+ data_layer: :embedded ,
270
+ extensions: [ AshJsonApi.Resource ]
271
+
272
+ json_api do
273
+ type ( "recursive-comment" )
274
+ end
275
+
276
+ attributes do
277
+ attribute ( :content , :string , public?: true )
278
+ attribute ( :parent , :struct , constraints: [ instance_of: __MODULE__ ] , public?: true )
279
+ attribute ( :replies , { :array , :struct } ,
280
+ constraints: [ items: [ instance_of: __MODULE__ ] ] ,
281
+ public?: true
282
+ )
283
+ end
284
+ end
285
+
286
+ defmodule ArticleWithComments do
287
+ use Ash.Resource ,
288
+ domain: Test.Acceptance.JsonSchemaTest.RecursiveInputTest.BlogDomain ,
289
+ data_layer: Ash.DataLayer.Ets ,
290
+ extensions: [ AshJsonApi.Resource ]
291
+
292
+ ets do
293
+ private? ( true )
294
+ end
295
+
296
+ json_api do
297
+ type ( "article-with-comments" )
298
+
299
+ routes do
300
+ base ( "/articles" )
301
+ get ( :read )
302
+ post ( :create )
303
+ patch ( :update )
304
+ end
305
+ end
306
+
307
+ actions do
308
+ default_accept ( :* )
309
+ defaults ( [ :read , :create , :update ] )
310
+ end
311
+
312
+ attributes do
313
+ uuid_primary_key ( :id , writable?: true )
314
+ attribute ( :title , :string , public?: true )
315
+ attribute ( :main_comment , RecursiveComment , public?: true )
316
+ end
317
+ end
318
+
319
+ defmodule BlogDomain do
320
+ use Ash.Domain ,
321
+ otp_app: :ash_json_api ,
322
+ extensions: [ AshJsonApi.Domain ]
323
+
324
+ json_api do
325
+ log_errors? ( false )
326
+ end
327
+
328
+ resources do
329
+ resource ( ArticleWithComments )
330
+ end
331
+ end
332
+ end
333
+
334
+ log =
335
+ capture_log ( fn ->
336
+ spec = AshJsonApi.OpenApi . spec ( domain: [ RecursiveInputTest.BlogDomain ] )
337
+
338
+ # Verify spec generation completes without stack overflow
339
+ assert % OpenApiSpex.OpenApi { } = spec
340
+
341
+ # Check that the create operation request body is properly generated
342
+ create_path = spec . paths [ "/articles" ]
343
+ assert create_path != nil
344
+
345
+ create_op = create_path . post
346
+ assert create_op != nil
347
+
348
+ # Verify the request body schema exists
349
+ assert create_op . requestBody != nil
350
+
351
+ # The key test is that we got here without stack overflow
352
+ # Additional checks to verify the schemas are properly structured
353
+ schemas = spec . components . schemas
354
+
355
+ # Verify the main resource schema exists
356
+ assert Map . has_key? ( schemas , "article-with-comments" )
357
+
358
+ # Verify the embedded recursive-comment schema exists
359
+ assert Map . has_key? ( schemas , "recursive-comment" )
360
+
361
+ # Check patch operation as well
362
+ patch_path = spec . paths [ "/articles/{id}" ]
363
+ assert patch_path != nil
364
+
365
+ patch_op = patch_path . patch
366
+ assert patch_op != nil
367
+ assert patch_op . requestBody != nil
368
+ end )
369
+
370
+ # The main assertion is that spec generation completes without stack overflow
371
+ # If our fix wasn't working, this would timeout or crash with SystemLimitError
372
+
373
+ # Additionally verify that warnings were logged for recursive input types
374
+ # This proves the recursion detection is working
375
+ if log != "" do
376
+ assert log =~ "recursive" or log =~ "Recursive" ,
377
+ "Expected some indication of recursive type handling in logs"
378
+ end
379
+ end
259
380
end
260
381
end
0 commit comments