lutaml-model 0.8.17 → 0.8.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/downstream-performance.yml +27 -1
  3. data/.github/workflows/js-pr-check.yml +102 -0
  4. data/.github/workflows/js-sync.yml +51 -0
  5. data/.github/workflows/opal.yml +26 -6
  6. data/.github/workflows/performance.yml +32 -7
  7. data/.github/workflows/rake.yml +78 -32
  8. data/.gitmodules +6 -0
  9. data/.rubocop_todo.yml +8 -0
  10. data/Gemfile +16 -1
  11. data/README.adoc +267 -0
  12. data/Rakefile +166 -4
  13. data/docs/_guides/builder-dsl.adoc +127 -0
  14. data/docs/_guides/schema-import.adoc +42 -0
  15. data/docs/_guides/xml-mapping.adoc +21 -0
  16. data/docs/_pages/validation.adoc +65 -0
  17. data/lib/compat/opal/generate_boot.rb +123 -0
  18. data/lib/compat/opal/js_bundle_entry.rb +42 -0
  19. data/lib/compat/opal/lutaml_model_boot.rb +497 -0
  20. data/lib/compat/opal/moxml_boot.rb +68 -0
  21. data/lib/compat/opal/yaml_compat.rb +32 -0
  22. data/lib/lutaml/json.rb +1 -1
  23. data/lib/lutaml/key_value/transform.rb +3 -8
  24. data/lib/lutaml/key_value/transformation/value_serializer.rb +14 -1
  25. data/lib/lutaml/key_value/transformation.rb +17 -5
  26. data/lib/lutaml/model/adapter_resolver.rb +2 -2
  27. data/lib/lutaml/model/attribute.rb +88 -11
  28. data/lib/lutaml/model/cached_type_resolver.rb +1 -1
  29. data/lib/lutaml/model/error/union_schema_unsupported_error.rb +11 -0
  30. data/lib/lutaml/model/global_context.rb +11 -8
  31. data/lib/lutaml/model/instrumentation.rb +2 -2
  32. data/lib/lutaml/model/mapping/mapping_rule.rb +0 -6
  33. data/lib/lutaml/model/register.rb +2 -2
  34. data/lib/lutaml/model/runtime_compatibility.rb +50 -0
  35. data/lib/lutaml/model/schema/class_loader.rb +58 -0
  36. data/lib/lutaml/model/schema/compiled_output.rb +83 -0
  37. data/lib/lutaml/model/schema/definitions/attribute.rb +32 -0
  38. data/lib/lutaml/model/schema/definitions/choice.rb +20 -0
  39. data/lib/lutaml/model/schema/definitions/facet.rb +40 -0
  40. data/lib/lutaml/model/schema/definitions/group_import.rb +22 -0
  41. data/lib/lutaml/model/schema/definitions/member_walk.rb +33 -0
  42. data/lib/lutaml/model/schema/definitions/model.rb +40 -0
  43. data/lib/lutaml/model/schema/definitions/namespace.rb +23 -0
  44. data/lib/lutaml/model/schema/definitions/restricted_type.rb +30 -0
  45. data/lib/lutaml/model/schema/definitions/sequence.rb +19 -0
  46. data/lib/lutaml/model/schema/definitions/simple_content.rb +22 -0
  47. data/lib/lutaml/model/schema/definitions/transform_facet.rb +21 -0
  48. data/lib/lutaml/model/schema/definitions/type_ref.rb +20 -0
  49. data/lib/lutaml/model/schema/definitions/union_type.rb +28 -0
  50. data/lib/lutaml/model/schema/definitions/xml_root.rb +20 -0
  51. data/lib/lutaml/model/schema/definitions.rb +24 -0
  52. data/lib/lutaml/model/schema/file_writer.rb +70 -0
  53. data/lib/lutaml/model/schema/generator/definitions_collection.rb +16 -2
  54. data/lib/lutaml/model/schema/generator/property.rb +21 -4
  55. data/lib/lutaml/model/schema/module_nesting.rb +31 -0
  56. data/lib/lutaml/model/schema/namespace_naming.rb +46 -0
  57. data/lib/lutaml/model/schema/registry_generator.rb +115 -0
  58. data/lib/lutaml/model/schema/renderers/base.rb +40 -0
  59. data/lib/lutaml/model/schema/renderers/mappings.rb +72 -0
  60. data/lib/lutaml/model/schema/renderers/member_decls.rb +115 -0
  61. data/lib/lutaml/model/schema/renderers/model.rb +121 -0
  62. data/lib/lutaml/model/schema/renderers/namespace.rb +30 -0
  63. data/lib/lutaml/model/schema/renderers/registration.rb +64 -0
  64. data/lib/lutaml/model/schema/renderers/required_files_calculator.rb +93 -0
  65. data/lib/lutaml/model/schema/renderers/restricted_type.rb +88 -0
  66. data/lib/lutaml/model/schema/renderers/union.rb +83 -0
  67. data/lib/lutaml/model/schema/renderers.rb +19 -0
  68. data/lib/lutaml/model/schema/rnc_compiler/source_resolver.rb +47 -0
  69. data/lib/lutaml/model/schema/rnc_compiler.rb +62 -0
  70. data/lib/lutaml/model/schema/rng_compiler/define_classifier.rb +91 -0
  71. data/lib/lutaml/model/schema/rng_compiler/element_visitor.rb +392 -0
  72. data/lib/lutaml/model/schema/rng_compiler/member_collector.rb +30 -0
  73. data/lib/lutaml/model/schema/rng_compiler/rng_helpers.rb +152 -0
  74. data/lib/lutaml/model/schema/rng_compiler/value_type_resolver.rb +125 -0
  75. data/lib/lutaml/model/schema/rng_compiler.rb +220 -0
  76. data/lib/lutaml/model/schema/templates.rb +162 -0
  77. data/lib/lutaml/model/schema/xml_compiler/element_order.rb +41 -0
  78. data/lib/lutaml/model/schema/xml_compiler/registry_generator.rb +31 -107
  79. data/lib/lutaml/model/schema/xml_compiler/spec_builder/complex_types.rb +191 -0
  80. data/lib/lutaml/model/schema/xml_compiler/spec_builder/members.rb +216 -0
  81. data/lib/lutaml/model/schema/xml_compiler/spec_builder/simple_types.rb +153 -0
  82. data/lib/lutaml/model/schema/xml_compiler/spec_builder.rb +167 -0
  83. data/lib/lutaml/model/schema/xml_compiler/supported_data_types.rb +87 -0
  84. data/lib/lutaml/model/schema/xml_compiler.rb +38 -517
  85. data/lib/lutaml/model/schema.rb +19 -0
  86. data/lib/lutaml/model/serialize/attribute_definition.rb +23 -11
  87. data/lib/lutaml/model/serialize/builder.rb +70 -2
  88. data/lib/lutaml/model/serialize/initialization.rb +3 -2
  89. data/lib/lutaml/model/serialize/value_mapping.rb +9 -57
  90. data/lib/lutaml/model/serialize.rb +6 -9
  91. data/lib/lutaml/model/services/rule_value_extractor.rb +2 -3
  92. data/lib/lutaml/model/services.rb +0 -2
  93. data/lib/lutaml/model/store.rb +2 -1
  94. data/lib/lutaml/model/toml.rb +1 -1
  95. data/lib/lutaml/model/transform.rb +17 -39
  96. data/lib/lutaml/model/transformation_registry.rb +1 -1
  97. data/lib/lutaml/model/type.rb +1 -0
  98. data/lib/lutaml/model/union.rb +320 -0
  99. data/lib/lutaml/model/utils.rb +6 -0
  100. data/lib/lutaml/model/validation.rb +1 -4
  101. data/lib/lutaml/model/version.rb +1 -1
  102. data/lib/lutaml/model.rb +18 -0
  103. data/lib/lutaml/toml.rb +1 -1
  104. data/lib/lutaml/xml/adapter.rb +1 -1
  105. data/lib/lutaml/xml/adapter_loader.rb +1 -1
  106. data/lib/lutaml/xml/builder/base.rb +1 -1
  107. data/lib/lutaml/xml/builder.rb +1 -1
  108. data/lib/lutaml/xml/error/schema_validation_error.rb +25 -0
  109. data/lib/lutaml/xml/model_transform.rb +5 -1
  110. data/lib/lutaml/xml/schema/relaxng_schema.rb +5 -0
  111. data/lib/lutaml/xml/schema/xsd_schema.rb +4 -0
  112. data/lib/lutaml/xml/serialization/format_conversion.rb +48 -0
  113. data/lib/lutaml/xml/serialization/instance_methods.rb +13 -0
  114. data/lib/lutaml/xml/transformation/element_builder.rb +21 -8
  115. data/lib/lutaml/xml/transformation/ordered_applier.rb +104 -4
  116. data/lib/lutaml/xml/xsd_validator.rb +66 -0
  117. data/lib/lutaml/xml.rb +53 -12
  118. data/lutaml-model.gemspec +4 -2
  119. data/spec/fixtures/xml/schema/rnc/address_book.rnc +15 -0
  120. data/spec/fixtures/xml/schema/rnc/book_features.rnc +18 -0
  121. data/spec/fixtures/xml/schema/rnc/includes/book.rnc +6 -0
  122. data/spec/fixtures/xml/schema/rnc/includes/circular_a.rnc +8 -0
  123. data/spec/fixtures/xml/schema/rnc/includes/circular_b.rnc +8 -0
  124. data/spec/fixtures/xml/schema/rnc/includes/library.rnc +8 -0
  125. data/spec/fixtures/xml/schema/rng/address_book.rng +16 -0
  126. data/spec/fixtures/xml/schema/rng/book.rng +27 -0
  127. data/spec/fixtures/xml/schema/rng/fixed_value.rng +11 -0
  128. data/spec/fixtures/xml/schema/rng/fragment_in_choice.rng +30 -0
  129. data/spec/fixtures/xml/schema/rng/full_name.rng +39 -0
  130. data/spec/fixtures/xml/schema/rng/integer_range.rng +32 -0
  131. data/spec/fixtures/xml/schema/rng/list_type.rng +12 -0
  132. data/spec/fixtures/xml/schema/rng/namespaced.rng +9 -0
  133. data/spec/fixtures/xml/schema/rng/paragraph.rng +13 -0
  134. data/spec/fixtures/xml/schema/rng/person.rng +23 -0
  135. data/spec/fixtures/xml/schema/rng/union.rng +14 -0
  136. data/spec/fixtures/xml/validate_xml_with_address.xsd +13 -0
  137. data/spec/fixtures/xml/validate_xml_with_contact.xsd +11 -0
  138. data/spec/fixtures/xml/validate_xml_with_person.xsd +11 -0
  139. data/spec/fixtures/xml/validate_xml_with_person_strict.xsd +17 -0
  140. data/spec/fixtures/xml/validate_xml_with_product.xsd +37 -0
  141. data/spec/lutaml/model/attribute_apply_value_map_spec.rb +134 -0
  142. data/spec/lutaml/model/attribute_spec.rb +169 -12
  143. data/spec/lutaml/model/boot_manifest_spec.rb +30 -0
  144. data/spec/lutaml/model/collection_spec.rb +18 -0
  145. data/spec/lutaml/model/mixed_content_spec.rb +171 -0
  146. data/spec/lutaml/model/opal_smoke_spec.rb +67 -2
  147. data/spec/lutaml/model/ordered_content_spec.rb +41 -0
  148. data/spec/lutaml/model/raw_element_spec.rb +125 -0
  149. data/spec/lutaml/model/rule_value_extractor_spec.rb +7 -14
  150. data/spec/lutaml/model/runtime_compatibility_spec.rb +25 -0
  151. data/spec/lutaml/model/schema/renderers/model_spec.rb +149 -0
  152. data/spec/lutaml/model/schema/renderers/namespace_spec.rb +36 -0
  153. data/spec/lutaml/model/schema/renderers/restricted_type_spec.rb +75 -0
  154. data/spec/lutaml/model/schema/renderers/union_spec.rb +58 -0
  155. data/spec/lutaml/model/serialize/builder_spec.rb +358 -0
  156. data/spec/lutaml/model/transform_apply_value_map_spec.rb +64 -0
  157. data/spec/lutaml/model/union_attribute_spec.rb +658 -0
  158. data/spec/lutaml/model/union_spec.rb +257 -0
  159. data/spec/lutaml/model/utils_spec.rb +14 -0
  160. data/spec/lutaml/xml/opal_xml_spec.rb +22 -20
  161. data/spec/lutaml/xml/schema/compiler_spec.rb +21 -4
  162. data/spec/lutaml/xml/schema/rnc_compiler_spec.rb +404 -0
  163. data/spec/lutaml/xml/schema/rng_compiler_spec.rb +893 -0
  164. data/spec/lutaml/xml/validate_xml_with_constructs_spec.rb +222 -0
  165. data/spec/lutaml/xml/validate_xml_with_spec.rb +186 -0
  166. data/spec/spec_helper.rb +28 -11
  167. data/spec/support/opal.rb +5 -2
  168. metadata +99 -31
  169. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +0 -106
  170. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +0 -47
  171. data/lib/lutaml/model/schema/xml_compiler/choice.rb +0 -67
  172. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +0 -27
  173. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +0 -34
  174. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +0 -173
  175. data/lib/lutaml/model/schema/xml_compiler/element.rb +0 -121
  176. data/lib/lutaml/model/schema/xml_compiler/group.rb +0 -113
  177. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +0 -104
  178. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +0 -52
  179. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +0 -40
  180. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +0 -265
  181. data/lib/lutaml/model/schema/xml_compiler/xml_namespace_class.rb +0 -110
  182. data/lib/lutaml/model/services/default_value_resolver.rb +0 -60
  183. data/spec/lutaml/model/services/default_value_resolver_spec.rb +0 -162
  184. data/spec/lutaml/xml/schema/compiler/attribute_group_spec.rb +0 -71
  185. data/spec/lutaml/xml/schema/compiler/attribute_spec.rb +0 -67
  186. data/spec/lutaml/xml/schema/compiler/choice_spec.rb +0 -74
  187. data/spec/lutaml/xml/schema/compiler/complex_content_restriction_spec.rb +0 -60
  188. data/spec/lutaml/xml/schema/compiler/complex_content_spec.rb +0 -41
  189. data/spec/lutaml/xml/schema/compiler/complex_type_spec.rb +0 -202
  190. data/spec/lutaml/xml/schema/compiler/element_spec.rb +0 -68
  191. data/spec/lutaml/xml/schema/compiler/group_spec.rb +0 -90
  192. data/spec/lutaml/xml/schema/compiler/restriction_spec.rb +0 -77
  193. data/spec/lutaml/xml/schema/compiler/sequence_spec.rb +0 -65
  194. data/spec/lutaml/xml/schema/compiler/simple_content_spec.rb +0 -61
  195. data/spec/lutaml/xml/schema/compiler/simple_type_spec.rb +0 -184
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module Definitions
7
+ # Stateless traversal helpers for the heterogeneous `members`
8
+ # array of a Definitions::Model. Definitions stay as pure data;
9
+ # the walk logic lives here so consumers (XmlCompiler /
10
+ # RngCompiler dependency collection, future visitors) don't each
11
+ # re-implement the same case-dispatch.
12
+ module MemberWalk
13
+ module_function
14
+
15
+ # Yields every Attribute leaf, recursing into Sequence members
16
+ # and Choice alternatives. Returns an Enumerator when called
17
+ # without a block.
18
+ def each_attribute(members, &block)
19
+ return enum_for(:each_attribute, members) unless block
20
+
21
+ members.each do |member|
22
+ case member
23
+ when Attribute then yield member
24
+ when Sequence then each_attribute(member.members, &block)
25
+ when Choice then each_attribute(member.alternatives, &block)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module Definitions
7
+ # One generated Lutaml::Model::Serializable subclass.
8
+ class Model
9
+ attr_accessor :class_name, :xml_root, :members,
10
+ :parent_class, :namespace_class_name,
11
+ :mixed, :text_content, :imports,
12
+ :documentation, :simple_content, :required_files,
13
+ :module_wrappable, :lazy_register
14
+
15
+ def initialize(class_name:, xml_root:, members: [],
16
+ parent_class: "Lutaml::Model::Serializable",
17
+ namespace_class_name: nil,
18
+ mixed: false, text_content: false,
19
+ imports: [], documentation: nil,
20
+ simple_content: nil, required_files: [],
21
+ module_wrappable: true, lazy_register: false)
22
+ @class_name = class_name
23
+ @xml_root = xml_root
24
+ @members = members
25
+ @parent_class = parent_class
26
+ @namespace_class_name = namespace_class_name
27
+ @mixed = mixed
28
+ @text_content = text_content
29
+ @imports = imports
30
+ @documentation = documentation
31
+ @simple_content = simple_content
32
+ @required_files = required_files
33
+ @module_wrappable = module_wrappable
34
+ @lazy_register = lazy_register
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module Definitions
7
+ # An XML namespace declaration (subclass of
8
+ # Lutaml::Xml::W3c::XmlNamespace).
9
+ class Namespace
10
+ attr_accessor :class_name, :uri, :prefix_default, :element_form_default
11
+
12
+ def initialize(class_name:, uri:, prefix_default: nil,
13
+ element_form_default: nil)
14
+ @class_name = class_name
15
+ @uri = uri
16
+ @prefix_default = prefix_default
17
+ @element_form_default = element_form_default
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module Definitions
7
+ # Restricted simple type — `class X < Lutaml::Model::Type::Y`
8
+ # with a cast body that applies facets and delegates to super.
9
+ class RestrictedType
10
+ attr_accessor :class_name, :parent_class, :facets,
11
+ :transform_facet, :required_files,
12
+ :keep_register_when_namespaced, :namespace_class_name
13
+
14
+ def initialize(class_name:, facets:, parent_class: nil,
15
+ transform_facet: nil, required_files: [],
16
+ keep_register_when_namespaced: false,
17
+ namespace_class_name: nil)
18
+ @class_name = class_name
19
+ @parent_class = parent_class
20
+ @facets = facets
21
+ @transform_facet = transform_facet
22
+ @required_files = required_files
23
+ @keep_register_when_namespaced = keep_register_when_namespaced
24
+ @namespace_class_name = namespace_class_name
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module Definitions
7
+ # A `sequence do ... end` group. Transparent in attribute
8
+ # declarations; wraps with `sequence do` only in xml mappings.
9
+ class Sequence
10
+ attr_accessor :members
11
+
12
+ def initialize(members:)
13
+ @members = members
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module Definitions
7
+ # XSD-only: a complex type with simple content (a typed text
8
+ # body plus optional decorating attributes). The renderer emits
9
+ # `attribute :content, :<base_type>` and any additional
10
+ # attributes after the regular members. RNG leaves this nil.
11
+ class SimpleContent
12
+ attr_accessor :base_class, :additional_attributes
13
+
14
+ def initialize(base_class:, additional_attributes: [])
15
+ @base_class = base_class
16
+ @additional_attributes = additional_attributes
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module Definitions
7
+ # XSD-only value transform applied during cast. The expression
8
+ # is raw Ruby emitted as `value = <expression>` before super.
9
+ # E.g. "value.gsub(/[\\r\\n\\t]/, ' ')" or "value.upcase". RNG
10
+ # leaves this nil.
11
+ class TransformFacet
12
+ attr_accessor :expression
13
+
14
+ def initialize(expression:)
15
+ @expression = expression
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module Definitions
7
+ # Reference to a Ruby type used in a generated attribute.
8
+ # kind ∈ {:symbol, :class_ref, :w3c}.
9
+ class TypeRef
10
+ attr_accessor :kind, :value
11
+
12
+ def initialize(kind:, value:)
13
+ @kind = kind
14
+ @value = value
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module Definitions
7
+ # Union type — Lutaml::Model::Type::Value subclass whose cast
8
+ # tries each member type and returns the first that parses.
9
+ # cast_strategy ∈ {:resolve_type, :class_refs}.
10
+ class UnionType
11
+ attr_accessor :class_name, :members, :cast_strategy, :required_files,
12
+ :lazy_register, :keep_register_when_namespaced
13
+
14
+ def initialize(class_name:, members:, cast_strategy:,
15
+ required_files: [], lazy_register: false,
16
+ keep_register_when_namespaced: false)
17
+ @class_name = class_name
18
+ @members = members
19
+ @cast_strategy = cast_strategy
20
+ @required_files = required_files
21
+ @lazy_register = lazy_register
22
+ @keep_register_when_namespaced = keep_register_when_namespaced
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module Definitions
7
+ # XML root directive of a generated class.
8
+ # kind ∈ {:element, :type_name, :fragment}. name is nil for :fragment.
9
+ class XmlRoot
10
+ attr_accessor :kind, :name
11
+
12
+ def initialize(kind:, name: nil)
13
+ @kind = kind
14
+ @name = name
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module Definitions
7
+ autoload :GroupImport, "#{__dir__}/definitions/group_import"
8
+ autoload :MemberWalk, "#{__dir__}/definitions/member_walk"
9
+ autoload :TypeRef, "#{__dir__}/definitions/type_ref"
10
+ autoload :XmlRoot, "#{__dir__}/definitions/xml_root"
11
+ autoload :Attribute, "#{__dir__}/definitions/attribute"
12
+ autoload :Choice, "#{__dir__}/definitions/choice"
13
+ autoload :Sequence, "#{__dir__}/definitions/sequence"
14
+ autoload :Facet, "#{__dir__}/definitions/facet"
15
+ autoload :TransformFacet, "#{__dir__}/definitions/transform_facet"
16
+ autoload :SimpleContent, "#{__dir__}/definitions/simple_content"
17
+ autoload :Model, "#{__dir__}/definitions/model"
18
+ autoload :RestrictedType, "#{__dir__}/definitions/restricted_type"
19
+ autoload :UnionType, "#{__dir__}/definitions/union_type"
20
+ autoload :Namespace, "#{__dir__}/definitions/namespace"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module Schema
8
+ # Writes a CompiledOutput to disk: per-class .rb files in a module
9
+ # subdirectory plus a central `*_registry.rb` (when a module
10
+ # namespace is set). Shared between XSD and RNG compilers.
11
+ #
12
+ # The `registry_generator:` parameter selects which RegistryGenerator
13
+ # subclass to use (XSD has a 2-phase template; RNG uses the default).
14
+ class FileWriter
15
+ def self.write(output, dir, registry_generator: RegistryGenerator)
16
+ new(output, dir, registry_generator).write
17
+ end
18
+
19
+ def initialize(output, dir, registry_generator)
20
+ @output = output
21
+ @dir = dir
22
+ @registry_generator = registry_generator
23
+ end
24
+
25
+ def write
26
+ if @output.module_namespace
27
+ write_namespaced
28
+ else
29
+ write_flat
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def write_namespaced
36
+ module_path = @output.module_namespace.split("::").map(&:downcase).join("/")
37
+ full_dir = File.join(@dir, module_path)
38
+ FileUtils.mkdir_p(full_dir)
39
+
40
+ registry = @registry_generator.generate(
41
+ @output.models,
42
+ module_namespace: @output.module_namespace,
43
+ register_id: @output.register_id,
44
+ namespaces: @output.namespaces,
45
+ )
46
+ if registry
47
+ registry_name = module_path.split("/").last
48
+ File.write(File.join(@dir, "#{registry_name}_registry.rb"), registry)
49
+ end
50
+
51
+ write_sources_to(full_dir)
52
+ end
53
+
54
+ def write_flat
55
+ FileUtils.mkdir_p(@dir)
56
+ write_sources_to(@dir)
57
+ end
58
+
59
+ def write_sources_to(target_dir)
60
+ @output.sources.each { |name, src| write_source(target_dir, name, src) }
61
+ end
62
+
63
+ def write_source(target_dir, name, src)
64
+ file_name = Utils.snake_case(Utils.last_of_split(name.to_s))
65
+ File.write(File.join(target_dir, "#{file_name}.rb"), src)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -19,9 +19,23 @@ module Lutaml
19
19
  def process_attributes(collection, klass)
20
20
  register = extract_register_from(klass)
21
21
  klass.attributes.each_value do |attribute|
22
- next unless attribute.serializable?(register)
22
+ if attribute.union?
23
+ process_union_members(collection, attribute)
24
+ elsif attribute.serializable?(register)
25
+ process_attribute(collection, attribute, register)
26
+ end
27
+ end
28
+ end
29
+
30
+ # A union attribute's type is Type::Union, not a model, so collect
31
+ # definitions for each of its Serializable members directly (a
32
+ # union schema emits a $ref per model member).
33
+ def process_union_members(collection, attribute)
34
+ attribute.union_member_types.each do |member|
35
+ next unless member.is_a?(::Class) &&
36
+ member.include?(Lutaml::Model::Serialize)
23
37
 
24
- process_attribute(collection, attribute, register)
38
+ collection.merge(DefinitionsCollection.from_class(member))
25
39
  end
26
40
  end
27
41
 
@@ -24,6 +24,8 @@ module Lutaml
24
24
  inside_collection = options.fetch(:inside_collection, false)
25
25
  if attr.collection? && !inside_collection
26
26
  collection_schema(attr)
27
+ elsif attr.union?
28
+ union_schema(attr, include_null: include_null)
27
29
  elsif attr.serializable?(register) && polymorphic?(attr)
28
30
  polymorphic_schema(attr)
29
31
  elsif attr.serializable?(register)
@@ -55,6 +57,22 @@ module Lutaml
55
57
  schema["maxItems"] = range.end if range.end
56
58
  end
57
59
 
60
+ # Union: anyOf (valid if the value matches at least one member), not
61
+ # oneOf (which would wrongly demand exactly one). A null branch is
62
+ # added for nullable attributes, mirroring primitive_schema, so an
63
+ # optional union still accepts null.
64
+ def union_schema(attr, include_null: true)
65
+ members = attr.union_member_types.map do |member|
66
+ if member.include?(Lutaml::Model::Serialize)
67
+ Ref.new(member).to_schema
68
+ else
69
+ { "type" => get_type(member) }
70
+ end
71
+ end
72
+ members << { "type" => "null" } if include_null
73
+ { "anyOf" => members }.merge(get_constraints(attr))
74
+ end
75
+
58
76
  def polymorphic_schema(attr)
59
77
  ref_schemas = attr.options[:polymorphic].map do |type|
60
78
  Ref.new(type).to_schema
@@ -82,10 +100,9 @@ module Lutaml
82
100
  constraints["pattern"] = attr.pattern.source if attr.pattern
83
101
 
84
102
  # Add default value
85
- resolver = Lutaml::Model::Services::DefaultValueResolver.new(attr,
86
- register, nil)
87
- if resolver.default_set?
88
- constraints["default"] = resolver.default
103
+ resolved = attr.default_value(register, nil)
104
+ unless Lutaml::Model::Utils.uninitialized?(resolved)
105
+ constraints["default"] = attr.cast_value(resolved, register)
89
106
  end
90
107
 
91
108
  # Add enumeration values
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ # Stateless helpers for emitting `module Foo ... end` wrappers around
7
+ # generated code, given an Array of module-name segments.
8
+ #
9
+ # Both helpers return a single newline-terminated block (no trailing
10
+ # newline) so ERB templates should use `<%= ... -%>` to avoid
11
+ # doubling the line break.
12
+ module ModuleNesting
13
+ module_function
14
+
15
+ def opening(modules)
16
+ return "" if modules.empty?
17
+
18
+ modules.map.with_index { |m, i| "#{' ' * i}module #{m}\n" }.join
19
+ end
20
+
21
+ def closing(modules)
22
+ return "" if modules.empty?
23
+
24
+ modules.reverse.map.with_index do |_m, i|
25
+ "#{' ' * (modules.size - i - 1)}end\n"
26
+ end.join
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module Schema
8
+ # Derives the class name and default prefix for a generated
9
+ # XmlNamespace subclass from a namespace URI.
10
+ #
11
+ # Used by both XSD and RNG compilers so the same URI always
12
+ # produces the same class name across formats.
13
+ module NamespaceNaming
14
+ module_function
15
+
16
+ def prefix_for(uri)
17
+ case uri
18
+ when %r{/math$} then "m"
19
+ when %r{XMLSchema} then "xs"
20
+ when %r{/(\w+)\z} then ::Regexp.last_match(1)[0..2]
21
+ else "ns"
22
+ end
23
+ end
24
+
25
+ def class_name_for(uri)
26
+ return "XmlSchemaNamespace" if uri.include?("XMLSchema")
27
+
28
+ parsed = URI.parse(uri)
29
+ host_parts = (parsed.host&.split(".") || []).reject(&:empty?)
30
+ path_parts = (parsed.path&.split("/") || []).reject(&:empty?)
31
+
32
+ name_parts = host_parts.reverse.take(2) + path_parts.last(2)
33
+ name_parts = name_parts.map do |p|
34
+ Utils.camel_case(p.gsub(/\d+/, "").gsub(/[^a-zA-Z]/, ""))
35
+ end.reject(&:empty?)
36
+
37
+ return "DefaultNamespace" if name_parts.empty?
38
+
39
+ "#{name_parts.join}Namespace"
40
+ rescue URI::InvalidURIError
41
+ "DefaultNamespace"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module Schema
8
+ # Base class for per-format registry generators (one Ruby file that
9
+ # declares autoloads + a `self.register_all` that registers every
10
+ # generated class with the model registry).
11
+ #
12
+ # Subclasses customize the ERB skeleton (e.g., XSD adds import
13
+ # resolution phases) but inherit the shared module-wrap and
14
+ # autoload/registration emit methods.
15
+ class RegistryGenerator
16
+ DEFAULT_TEMPLATE = ERB.new(<<~TMPL, trim_mode: "-")
17
+ # frozen_string_literal: true
18
+ # Auto-generated central registry for <%= @module_namespace %>
19
+
20
+ <%= module_opening -%>
21
+ <%= autoload_declarations %>
22
+
23
+ def self.register_all
24
+ context = Lutaml::Model::GlobalContext.context(:<%= @register_id %>) ||
25
+ Lutaml::Model::GlobalContext.create_context(id: :<%= @register_id %>)
26
+
27
+ <%= registration_body %>
28
+ end
29
+ <%= module_closing -%>
30
+ TMPL
31
+
32
+ def self.generate(model_entries, options = {})
33
+ new(model_entries, options).generate
34
+ end
35
+
36
+ # `model_entries` is an Array of CompiledOutput::Entry with kind
37
+ # `:model`. `options[:namespaces]` is an Array of `:namespace`
38
+ # entries, autoloaded but never registered.
39
+ def initialize(model_entries, options)
40
+ @classes = model_entries
41
+ @namespaces = options[:namespaces] || []
42
+ @module_namespace = options[:module_namespace]
43
+ @register_id = options[:register_id] || :default
44
+ @modules = @module_namespace&.split("::") || []
45
+ end
46
+
47
+ def generate
48
+ return nil unless @module_namespace
49
+
50
+ template.result(binding)
51
+ end
52
+
53
+ private
54
+
55
+ # Subclasses can override to use a different ERB skeleton.
56
+ def template
57
+ self.class::DEFAULT_TEMPLATE
58
+ end
59
+
60
+ # The body of `def self.register_all`. Default is just the
61
+ # registration block; subclasses can override to add extra phases
62
+ # (e.g., XSD's ensure_imports! / ensure_mappings_imported! passes).
63
+ def registration_body
64
+ registration_calls
65
+ end
66
+
67
+ # Inside-`register_all` indent, used by phase-comment overrides.
68
+ def body_indent
69
+ " " * (module_depth + 1)
70
+ end
71
+
72
+ # Autoload every generated constant: model classes AND namespace
73
+ # classes. Models are then registered with `register_all`;
74
+ # namespaces are only autoloaded (model classes reference them
75
+ # in their `namespace ...` mapping declarations).
76
+ def autoload_declarations
77
+ indent = " " * module_depth
78
+ module_subdir = @module_namespace.split("::").last.downcase
79
+ (@classes + @namespaces).map do |entry|
80
+ class_name = Utils.camel_case(entry.name)
81
+ file_name = Utils.snake_case(entry.name)
82
+ "#{indent}autoload :#{class_name}, " \
83
+ "File.join(__dir__, \"#{module_subdir}\", \"#{file_name}\")"
84
+ end.join("\n")
85
+ end
86
+
87
+ def registration_calls
88
+ indent = " " * (module_depth + 1)
89
+ @classes.map do |entry|
90
+ class_name = Utils.camel_case(entry.name)
91
+ id = Utils.snake_case(entry.name).to_sym
92
+ "#{indent}#{class_name} # Trigger autoload\n" \
93
+ "#{indent}context.registry.register(:#{id}, #{class_name})\n" \
94
+ "#{indent}#{class_name}.instance_variable_set(:@register, :#{@register_id})"
95
+ end.join("\n")
96
+ end
97
+
98
+ # Thin forwarders so the ERB template using `<%= module_opening %>`
99
+ # / `<%= module_closing %>` keeps working. Logic lives in
100
+ # `Lutaml::Model::Schema::ModuleNesting`.
101
+ def module_opening
102
+ ModuleNesting.opening(Array(@modules))
103
+ end
104
+
105
+ def module_closing
106
+ ModuleNesting.closing(Array(@modules))
107
+ end
108
+
109
+ def module_depth
110
+ @modules.size
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module Schema
8
+ module Renderers
9
+ # Shared constructor and helpers for the value/namespace renderers
10
+ # (Union, RestrictedType, Namespace). Model has its own constructor
11
+ # because it computes an opt-out (module-wrappable) namespace.
12
+ class Base
13
+ def self.render(spec, **)
14
+ new(spec, **).render
15
+ end
16
+
17
+ def initialize(spec, indent: 2, module_namespace: nil, register_id: :default)
18
+ @spec = spec
19
+ @indent = indent.is_a?(Integer) ? " " * indent : indent
20
+ @extended_indent = @indent * 2
21
+ @module_namespace = module_namespace
22
+ @modules = module_namespace&.split("::") || []
23
+ @register_id = register_id
24
+ end
25
+
26
+ private
27
+
28
+ def required_files_block
29
+ files = @spec.required_files
30
+ files.empty? ? "" : "#{files.uniq.join("\n")}\n"
31
+ end
32
+
33
+ def module_opening = ModuleNesting.opening(@modules)
34
+ def module_closing = ModuleNesting.closing(@modules)
35
+ def boilerplate_indent_str = @indent
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end