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.
- checksums.yaml +4 -4
- data/.github/workflows/downstream-performance.yml +27 -1
- data/.github/workflows/js-pr-check.yml +102 -0
- data/.github/workflows/js-sync.yml +51 -0
- data/.github/workflows/opal.yml +26 -6
- data/.github/workflows/performance.yml +32 -7
- data/.github/workflows/rake.yml +78 -32
- data/.gitmodules +6 -0
- data/.rubocop_todo.yml +8 -0
- data/Gemfile +16 -1
- data/README.adoc +267 -0
- data/Rakefile +166 -4
- data/docs/_guides/builder-dsl.adoc +127 -0
- data/docs/_guides/schema-import.adoc +42 -0
- data/docs/_guides/xml-mapping.adoc +21 -0
- data/docs/_pages/validation.adoc +65 -0
- data/lib/compat/opal/generate_boot.rb +123 -0
- data/lib/compat/opal/js_bundle_entry.rb +42 -0
- data/lib/compat/opal/lutaml_model_boot.rb +497 -0
- data/lib/compat/opal/moxml_boot.rb +68 -0
- data/lib/compat/opal/yaml_compat.rb +32 -0
- data/lib/lutaml/json.rb +1 -1
- data/lib/lutaml/key_value/transform.rb +3 -8
- data/lib/lutaml/key_value/transformation/value_serializer.rb +14 -1
- data/lib/lutaml/key_value/transformation.rb +17 -5
- data/lib/lutaml/model/adapter_resolver.rb +2 -2
- data/lib/lutaml/model/attribute.rb +88 -11
- data/lib/lutaml/model/cached_type_resolver.rb +1 -1
- data/lib/lutaml/model/error/union_schema_unsupported_error.rb +11 -0
- data/lib/lutaml/model/global_context.rb +11 -8
- data/lib/lutaml/model/instrumentation.rb +2 -2
- data/lib/lutaml/model/mapping/mapping_rule.rb +0 -6
- data/lib/lutaml/model/register.rb +2 -2
- data/lib/lutaml/model/runtime_compatibility.rb +50 -0
- data/lib/lutaml/model/schema/class_loader.rb +58 -0
- data/lib/lutaml/model/schema/compiled_output.rb +83 -0
- data/lib/lutaml/model/schema/definitions/attribute.rb +32 -0
- data/lib/lutaml/model/schema/definitions/choice.rb +20 -0
- data/lib/lutaml/model/schema/definitions/facet.rb +40 -0
- data/lib/lutaml/model/schema/definitions/group_import.rb +22 -0
- data/lib/lutaml/model/schema/definitions/member_walk.rb +33 -0
- data/lib/lutaml/model/schema/definitions/model.rb +40 -0
- data/lib/lutaml/model/schema/definitions/namespace.rb +23 -0
- data/lib/lutaml/model/schema/definitions/restricted_type.rb +30 -0
- data/lib/lutaml/model/schema/definitions/sequence.rb +19 -0
- data/lib/lutaml/model/schema/definitions/simple_content.rb +22 -0
- data/lib/lutaml/model/schema/definitions/transform_facet.rb +21 -0
- data/lib/lutaml/model/schema/definitions/type_ref.rb +20 -0
- data/lib/lutaml/model/schema/definitions/union_type.rb +28 -0
- data/lib/lutaml/model/schema/definitions/xml_root.rb +20 -0
- data/lib/lutaml/model/schema/definitions.rb +24 -0
- data/lib/lutaml/model/schema/file_writer.rb +70 -0
- data/lib/lutaml/model/schema/generator/definitions_collection.rb +16 -2
- data/lib/lutaml/model/schema/generator/property.rb +21 -4
- data/lib/lutaml/model/schema/module_nesting.rb +31 -0
- data/lib/lutaml/model/schema/namespace_naming.rb +46 -0
- data/lib/lutaml/model/schema/registry_generator.rb +115 -0
- data/lib/lutaml/model/schema/renderers/base.rb +40 -0
- data/lib/lutaml/model/schema/renderers/mappings.rb +72 -0
- data/lib/lutaml/model/schema/renderers/member_decls.rb +115 -0
- data/lib/lutaml/model/schema/renderers/model.rb +121 -0
- data/lib/lutaml/model/schema/renderers/namespace.rb +30 -0
- data/lib/lutaml/model/schema/renderers/registration.rb +64 -0
- data/lib/lutaml/model/schema/renderers/required_files_calculator.rb +93 -0
- data/lib/lutaml/model/schema/renderers/restricted_type.rb +88 -0
- data/lib/lutaml/model/schema/renderers/union.rb +83 -0
- data/lib/lutaml/model/schema/renderers.rb +19 -0
- data/lib/lutaml/model/schema/rnc_compiler/source_resolver.rb +47 -0
- data/lib/lutaml/model/schema/rnc_compiler.rb +62 -0
- data/lib/lutaml/model/schema/rng_compiler/define_classifier.rb +91 -0
- data/lib/lutaml/model/schema/rng_compiler/element_visitor.rb +392 -0
- data/lib/lutaml/model/schema/rng_compiler/member_collector.rb +30 -0
- data/lib/lutaml/model/schema/rng_compiler/rng_helpers.rb +152 -0
- data/lib/lutaml/model/schema/rng_compiler/value_type_resolver.rb +125 -0
- data/lib/lutaml/model/schema/rng_compiler.rb +220 -0
- data/lib/lutaml/model/schema/templates.rb +162 -0
- data/lib/lutaml/model/schema/xml_compiler/element_order.rb +41 -0
- data/lib/lutaml/model/schema/xml_compiler/registry_generator.rb +31 -107
- data/lib/lutaml/model/schema/xml_compiler/spec_builder/complex_types.rb +191 -0
- data/lib/lutaml/model/schema/xml_compiler/spec_builder/members.rb +216 -0
- data/lib/lutaml/model/schema/xml_compiler/spec_builder/simple_types.rb +153 -0
- data/lib/lutaml/model/schema/xml_compiler/spec_builder.rb +167 -0
- data/lib/lutaml/model/schema/xml_compiler/supported_data_types.rb +87 -0
- data/lib/lutaml/model/schema/xml_compiler.rb +38 -517
- data/lib/lutaml/model/schema.rb +19 -0
- data/lib/lutaml/model/serialize/attribute_definition.rb +23 -11
- data/lib/lutaml/model/serialize/builder.rb +70 -2
- data/lib/lutaml/model/serialize/initialization.rb +3 -2
- data/lib/lutaml/model/serialize/value_mapping.rb +9 -57
- data/lib/lutaml/model/serialize.rb +6 -9
- data/lib/lutaml/model/services/rule_value_extractor.rb +2 -3
- data/lib/lutaml/model/services.rb +0 -2
- data/lib/lutaml/model/store.rb +2 -1
- data/lib/lutaml/model/toml.rb +1 -1
- data/lib/lutaml/model/transform.rb +17 -39
- data/lib/lutaml/model/transformation_registry.rb +1 -1
- data/lib/lutaml/model/type.rb +1 -0
- data/lib/lutaml/model/union.rb +320 -0
- data/lib/lutaml/model/utils.rb +6 -0
- data/lib/lutaml/model/validation.rb +1 -4
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +18 -0
- data/lib/lutaml/toml.rb +1 -1
- data/lib/lutaml/xml/adapter.rb +1 -1
- data/lib/lutaml/xml/adapter_loader.rb +1 -1
- data/lib/lutaml/xml/builder/base.rb +1 -1
- data/lib/lutaml/xml/builder.rb +1 -1
- data/lib/lutaml/xml/error/schema_validation_error.rb +25 -0
- data/lib/lutaml/xml/model_transform.rb +5 -1
- data/lib/lutaml/xml/schema/relaxng_schema.rb +5 -0
- data/lib/lutaml/xml/schema/xsd_schema.rb +4 -0
- data/lib/lutaml/xml/serialization/format_conversion.rb +48 -0
- data/lib/lutaml/xml/serialization/instance_methods.rb +13 -0
- data/lib/lutaml/xml/transformation/element_builder.rb +21 -8
- data/lib/lutaml/xml/transformation/ordered_applier.rb +104 -4
- data/lib/lutaml/xml/xsd_validator.rb +66 -0
- data/lib/lutaml/xml.rb +53 -12
- data/lutaml-model.gemspec +4 -2
- data/spec/fixtures/xml/schema/rnc/address_book.rnc +15 -0
- data/spec/fixtures/xml/schema/rnc/book_features.rnc +18 -0
- data/spec/fixtures/xml/schema/rnc/includes/book.rnc +6 -0
- data/spec/fixtures/xml/schema/rnc/includes/circular_a.rnc +8 -0
- data/spec/fixtures/xml/schema/rnc/includes/circular_b.rnc +8 -0
- data/spec/fixtures/xml/schema/rnc/includes/library.rnc +8 -0
- data/spec/fixtures/xml/schema/rng/address_book.rng +16 -0
- data/spec/fixtures/xml/schema/rng/book.rng +27 -0
- data/spec/fixtures/xml/schema/rng/fixed_value.rng +11 -0
- data/spec/fixtures/xml/schema/rng/fragment_in_choice.rng +30 -0
- data/spec/fixtures/xml/schema/rng/full_name.rng +39 -0
- data/spec/fixtures/xml/schema/rng/integer_range.rng +32 -0
- data/spec/fixtures/xml/schema/rng/list_type.rng +12 -0
- data/spec/fixtures/xml/schema/rng/namespaced.rng +9 -0
- data/spec/fixtures/xml/schema/rng/paragraph.rng +13 -0
- data/spec/fixtures/xml/schema/rng/person.rng +23 -0
- data/spec/fixtures/xml/schema/rng/union.rng +14 -0
- data/spec/fixtures/xml/validate_xml_with_address.xsd +13 -0
- data/spec/fixtures/xml/validate_xml_with_contact.xsd +11 -0
- data/spec/fixtures/xml/validate_xml_with_person.xsd +11 -0
- data/spec/fixtures/xml/validate_xml_with_person_strict.xsd +17 -0
- data/spec/fixtures/xml/validate_xml_with_product.xsd +37 -0
- data/spec/lutaml/model/attribute_apply_value_map_spec.rb +134 -0
- data/spec/lutaml/model/attribute_spec.rb +169 -12
- data/spec/lutaml/model/boot_manifest_spec.rb +30 -0
- data/spec/lutaml/model/collection_spec.rb +18 -0
- data/spec/lutaml/model/mixed_content_spec.rb +171 -0
- data/spec/lutaml/model/opal_smoke_spec.rb +67 -2
- data/spec/lutaml/model/ordered_content_spec.rb +41 -0
- data/spec/lutaml/model/raw_element_spec.rb +125 -0
- data/spec/lutaml/model/rule_value_extractor_spec.rb +7 -14
- data/spec/lutaml/model/runtime_compatibility_spec.rb +25 -0
- data/spec/lutaml/model/schema/renderers/model_spec.rb +149 -0
- data/spec/lutaml/model/schema/renderers/namespace_spec.rb +36 -0
- data/spec/lutaml/model/schema/renderers/restricted_type_spec.rb +75 -0
- data/spec/lutaml/model/schema/renderers/union_spec.rb +58 -0
- data/spec/lutaml/model/serialize/builder_spec.rb +358 -0
- data/spec/lutaml/model/transform_apply_value_map_spec.rb +64 -0
- data/spec/lutaml/model/union_attribute_spec.rb +658 -0
- data/spec/lutaml/model/union_spec.rb +257 -0
- data/spec/lutaml/model/utils_spec.rb +14 -0
- data/spec/lutaml/xml/opal_xml_spec.rb +22 -20
- data/spec/lutaml/xml/schema/compiler_spec.rb +21 -4
- data/spec/lutaml/xml/schema/rnc_compiler_spec.rb +404 -0
- data/spec/lutaml/xml/schema/rng_compiler_spec.rb +893 -0
- data/spec/lutaml/xml/validate_xml_with_constructs_spec.rb +222 -0
- data/spec/lutaml/xml/validate_xml_with_spec.rb +186 -0
- data/spec/spec_helper.rb +28 -11
- data/spec/support/opal.rb +5 -2
- metadata +99 -31
- data/lib/lutaml/model/schema/xml_compiler/attribute.rb +0 -106
- data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +0 -47
- data/lib/lutaml/model/schema/xml_compiler/choice.rb +0 -67
- data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +0 -27
- data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +0 -34
- data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +0 -173
- data/lib/lutaml/model/schema/xml_compiler/element.rb +0 -121
- data/lib/lutaml/model/schema/xml_compiler/group.rb +0 -113
- data/lib/lutaml/model/schema/xml_compiler/restriction.rb +0 -104
- data/lib/lutaml/model/schema/xml_compiler/sequence.rb +0 -52
- data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +0 -40
- data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +0 -265
- data/lib/lutaml/model/schema/xml_compiler/xml_namespace_class.rb +0 -110
- data/lib/lutaml/model/services/default_value_resolver.rb +0 -60
- data/spec/lutaml/model/services/default_value_resolver_spec.rb +0 -162
- data/spec/lutaml/xml/schema/compiler/attribute_group_spec.rb +0 -71
- data/spec/lutaml/xml/schema/compiler/attribute_spec.rb +0 -67
- data/spec/lutaml/xml/schema/compiler/choice_spec.rb +0 -74
- data/spec/lutaml/xml/schema/compiler/complex_content_restriction_spec.rb +0 -60
- data/spec/lutaml/xml/schema/compiler/complex_content_spec.rb +0 -41
- data/spec/lutaml/xml/schema/compiler/complex_type_spec.rb +0 -202
- data/spec/lutaml/xml/schema/compiler/element_spec.rb +0 -68
- data/spec/lutaml/xml/schema/compiler/group_spec.rb +0 -90
- data/spec/lutaml/xml/schema/compiler/restriction_spec.rb +0 -77
- data/spec/lutaml/xml/schema/compiler/sequence_spec.rb +0 -65
- data/spec/lutaml/xml/schema/compiler/simple_content_spec.rb +0 -61
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|