@@ -381,6 +381,26 @@ defmodule Spark.Dsl.Extension do
381381 end
382382 end
383383
384+ def set_docs ( items ) when is_list ( items ) do
385+ Enum . map ( items , & set_docs / 1 )
386+ end
387+
388+ def set_docs ( % Spark.Dsl.Entity { } = entity ) do
389+ entity
390+ |> Map . put ( :docs , Spark.Dsl.Extension . doc_entity ( entity ) )
391+ |> Map . put (
392+ :entities ,
393+ Enum . map ( entity . entities || [ ] , fn { key , value } -> { key , set_docs ( value ) } end )
394+ )
395+ end
396+
397+ def set_docs ( % Spark.Dsl.Section { } = section ) do
398+ section
399+ |> Map . put ( :entities , set_docs ( section . entities ) )
400+ |> Map . put ( :sections , set_docs ( section . sections ) )
401+ |> Map . put ( :docs , Spark.Dsl.Extension . doc_section ( section ) )
402+ end
403+
384404 @ doc false
385405 defmacro __using__ ( opts ) do
386406 quote bind_quoted: [
@@ -397,19 +417,23 @@ defmodule Spark.Dsl.Extension do
397417 alias Spark.Dsl.Extension
398418 module_prefix = module_prefix || __MODULE__
399419
400- @ behaviour Extension
401- Extension . build ( __MODULE__ , module_prefix , sections , dsl_patches )
402420 @ _sections sections
421+ |> Enum . map ( & Extension . validate_and_transform_section ( & 1 , __MODULE__ ) )
422+ |> Extension . set_docs ( )
423+ @ _dsl_patches Extension . validate_and_transform_dsl_patches ( dsl_patches , __MODULE__ )
403424 @ _transformers transformers
404425 @ _verifiers verifiers
405426 @ _persisters persisters
406- @ _dsl_patches dsl_patches
407427 @ _imports imports
408428 @ _add_extensions add_extensions
429+
430+ @ behaviour Extension
431+ Extension . build ( __MODULE__ , module_prefix , @ _sections , @ _dsl_patches )
432+
409433 @ after_verify Spark.Dsl.Extension
410434
411435 @ doc false
412- def sections , do: set_docs ( @ _sections )
436+ def sections , do: @ _sections
413437 @ doc false
414438 def verifiers , do: [ Spark.Dsl.Verifiers.VerifyEntityUniqueness | @ _verifiers ]
415439 @ doc false
@@ -424,26 +448,6 @@ defmodule Spark.Dsl.Extension do
424448 def dsl_patches , do: @ _dsl_patches
425449 @ doc false
426450 def add_extensions , do: @ _add_extensions
427-
428- defp set_docs ( items ) when is_list ( items ) do
429- Enum . map ( items , & set_docs / 1 )
430- end
431-
432- defp set_docs ( % Spark.Dsl.Entity { } = entity ) do
433- entity
434- |> Map . put ( :docs , Spark.Dsl.Extension . doc_entity ( entity ) )
435- |> Map . put (
436- :entities ,
437- Enum . map ( entity . entities || [ ] , fn { key , value } -> { key , set_docs ( value ) } end )
438- )
439- end
440-
441- defp set_docs ( % Spark.Dsl.Section { } = section ) do
442- section
443- |> Map . put ( :entities , set_docs ( section . entities ) )
444- |> Map . put ( :sections , set_docs ( section . sections ) )
445- |> Map . put ( :docs , Spark.Dsl.Extension . doc_section ( section ) )
446- end
447451 end
448452 end
449453
@@ -834,7 +838,7 @@ defmodule Spark.Dsl.Extension do
834838 % Spark.Dsl.Patch.AddEntity { entity: entity } = dsl_patch ,
835839 module
836840 ) do
837- entity = Spark.Dsl.Entity . validate_and_transform ( entity , [ ] , module )
841+ entity = validate_and_transform_entity ( entity , [ ] , module )
838842 % { dsl_patch | entity: entity }
839843 end
840844
@@ -855,8 +859,6 @@ defmodule Spark.Dsl.Extension do
855859 ] do
856860 alias Spark.Dsl.Extension
857861
858- dsl_patches = Extension . validate_and_transform_dsl_patches ( dsl_patches , __MODULE__ )
859-
860862 { :ok , agent } = Agent . start_link ( fn -> [ ] end )
861863 agent_and_pid = { agent , self ( ) }
862864
@@ -904,11 +906,63 @@ defmodule Spark.Dsl.Extension do
904906 ) do
905907 % {
906908 section
907- | entities:
908- Enum . map ( entities , & Spark.Dsl.Entity . validate_and_transform ( & 1 , [ section . name ] , module ) )
909+ | entities: Enum . map ( entities , & validate_and_transform_entity ( & 1 , [ section . name ] , module ) )
909910 }
910911 end
911912
913+ @ doc """
914+ Validates and transforms an entity structure, ensuring nested entities are properly formatted.
915+
916+ This function recursively processes a DSL entity and its nested entities, converting
917+ single entity values to lists where needed and validating the structure.
918+
919+ ## Parameters
920+
921+ - `entity` - The entity to validate and transform
922+ - `path` - The current path in the DSL structure (for error reporting)
923+ - `module` - The module context (for error reporting)
924+
925+ ## Returns
926+
927+ Returns the transformed entity with normalized nested entity structures.
928+ """
929+ def validate_and_transform_entity ( entity , path \\ [ ] , module \\ nil )
930+
931+ def validate_and_transform_entity ( % Spark.Dsl.Entity { } = entity , path , module ) do
932+ # Include the entity's name in the path when processing nested entities
933+ nested_path = if entity . name , do: path ++ [ entity . name ] , else: path
934+
935+ entities =
936+ entity . entities
937+ |> List . wrap ( )
938+ |> Enum . map ( fn
939+ { key , % Spark.Dsl.Entity { } = value } ->
940+ { key , [ validate_and_transform_entity ( value , nested_path ++ [ key ] , module ) ] }
941+
942+ { key , values } when is_list ( values ) ->
943+ # Already a list, keep as is
944+ { key ,
945+ Enum . map ( values , & validate_and_transform_entity ( & 1 , nested_path ++ [ key ] , module ) ) }
946+
947+ { key , value } ->
948+ # Non-entity, non-list value - this is invalid
949+ raise Spark.Error.DslError ,
950+ module: module ,
951+ path: nested_path ++ [ key ] ,
952+ message:
953+ "nested entity '#{ key } ' must be an entity or list of entities, got: #{ inspect ( value ) } "
954+ end )
955+
956+ % { entity | entities: entities }
957+ end
958+
959+ def validate_and_transform_entity ( _ , path , module ) do
960+ raise Spark.Error.DslError ,
961+ module: module ,
962+ path: path ,
963+ message: "Invalid entity structure"
964+ end
965+
912966 @ doc false
913967 defmacro build_section (
914968 agent ,
@@ -927,8 +981,6 @@ defmodule Spark.Dsl.Extension do
927981 generated: true do
928982 alias Spark.Dsl
929983
930- section = Dsl.Extension . validate_and_transform_section ( section , __MODULE__ )
931-
932984 { section_modules , entity_modules , opts_module } =
933985 Dsl.Extension . do_build_section (
934986 agent ,
0 commit comments