lutaml-model 0.8.17 → 0.8.18
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/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 +14 -2
- data/lib/lutaml/model/serialize/builder.rb +15 -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 +6 -0
- 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 +30 -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/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 +97 -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,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Opal-only boot for moxml — mirrors moxml's shipped
|
|
4
|
+
# `lib/compat/opal/moxml_boot.rb` so both Oga and REXML adapters are
|
|
5
|
+
# eager-loaded (Opal ignores autoload; without this, nested Moxml
|
|
6
|
+
# constants NameError at runtime).
|
|
7
|
+
#
|
|
8
|
+
# Requires the REXML gem's lib dir to be on Opal's load path so
|
|
9
|
+
# `require "rexml/formatters/pretty"` (pulled in by moxml's
|
|
10
|
+
# customized_rexml/formatter) resolves. The Rakefile sets that up.
|
|
11
|
+
#
|
|
12
|
+
# moxml's own boot loads the same list. We maintain our own copy so we
|
|
13
|
+
# can reorder or gate entries if a future moxml release introduces an
|
|
14
|
+
# Opal-incompatible file before its shipped boot catches up.
|
|
15
|
+
|
|
16
|
+
if RUBY_ENGINE == "opal"
|
|
17
|
+
require "moxml/version"
|
|
18
|
+
require "moxml/error"
|
|
19
|
+
require "moxml/native_attachment"
|
|
20
|
+
require "moxml/native_attachment/opal"
|
|
21
|
+
require "moxml/xml_utils"
|
|
22
|
+
require "moxml/xml_utils/encoder"
|
|
23
|
+
require "moxml/node"
|
|
24
|
+
require "moxml/node_set"
|
|
25
|
+
require "moxml/document"
|
|
26
|
+
require "moxml/element"
|
|
27
|
+
require "moxml/attribute"
|
|
28
|
+
require "moxml/text"
|
|
29
|
+
require "moxml/cdata"
|
|
30
|
+
require "moxml/comment"
|
|
31
|
+
require "moxml/processing_instruction"
|
|
32
|
+
require "moxml/declaration"
|
|
33
|
+
require "moxml/namespace"
|
|
34
|
+
require "moxml/doctype"
|
|
35
|
+
require "moxml/entity_reference"
|
|
36
|
+
require "moxml/entity_registry"
|
|
37
|
+
require "moxml/adapter"
|
|
38
|
+
require "moxml/adapter/base"
|
|
39
|
+
require "moxml/adapter/customized_rexml"
|
|
40
|
+
require "moxml/adapter/customized_rexml/entity_reference"
|
|
41
|
+
require "moxml/adapter/customized_rexml/formatter"
|
|
42
|
+
require "moxml/sax"
|
|
43
|
+
require "moxml/sax/handler"
|
|
44
|
+
require "moxml/sax/element_handler"
|
|
45
|
+
require "moxml/sax/block_handler"
|
|
46
|
+
require "moxml/sax/namespace_splitter"
|
|
47
|
+
require "moxml/adapter/rexml"
|
|
48
|
+
require "moxml/adapter/customized_oga"
|
|
49
|
+
require "moxml/adapter/customized_oga/xml_declaration"
|
|
50
|
+
require "moxml/adapter/customized_oga/xml_generator"
|
|
51
|
+
require "moxml/adapter/oga"
|
|
52
|
+
require "moxml/document_builder"
|
|
53
|
+
require "moxml/builder"
|
|
54
|
+
require "moxml/context"
|
|
55
|
+
require "moxml/config"
|
|
56
|
+
require "moxml/xpath"
|
|
57
|
+
require "moxml/xpath/engine"
|
|
58
|
+
require "moxml/xpath/context"
|
|
59
|
+
require "moxml/xpath/conversion"
|
|
60
|
+
require "moxml/xpath/cache"
|
|
61
|
+
require "moxml/xpath/lexer"
|
|
62
|
+
require "moxml/xpath/parser"
|
|
63
|
+
require "moxml/xpath/compiler"
|
|
64
|
+
require "moxml/xpath/errors"
|
|
65
|
+
require "moxml/xpath/ast/node"
|
|
66
|
+
require "moxml/xpath/ruby/node"
|
|
67
|
+
require "moxml/xpath/ruby/generator"
|
|
68
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Opal runtime YAML compat.
|
|
4
|
+
#
|
|
5
|
+
# Opal's `nodejs/yaml` ships `YAML.load` only. lutaml-model's YAML adapter
|
|
6
|
+
# uses `YAML.safe_load(src, permitted_classes: [...])` and `YAML.dump(obj)`.
|
|
7
|
+
# Provide both as thin wrappers — nodejs/yaml is already backed by
|
|
8
|
+
# jsyaml.safeLoad, so `load` semantics are equivalent to MRI's safe_load
|
|
9
|
+
# with the default permitted classes (the perm_classes argument is
|
|
10
|
+
# accepted but ignored, matching Opal's stronger-by-default posture).
|
|
11
|
+
|
|
12
|
+
if RUBY_ENGINE == "opal"
|
|
13
|
+
require "nodejs/yaml"
|
|
14
|
+
|
|
15
|
+
module ::YAML
|
|
16
|
+
class << self
|
|
17
|
+
# Accept MRI's full safe_load kwargs for API parity; nodejs/yaml's
|
|
18
|
+
# safeLoad is already safe-by-default so the kwargs are unused.
|
|
19
|
+
def safe_load(yaml, **_kwargs)
|
|
20
|
+
load(yaml)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def dump(obj, io = nil)
|
|
24
|
+
dumped = `#{@__yaml__}.safeDump(#{obj})`
|
|
25
|
+
return dumped unless io
|
|
26
|
+
|
|
27
|
+
io.write(dumped)
|
|
28
|
+
io
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/lutaml/json.rb
CHANGED
|
@@ -34,7 +34,7 @@ module Lutaml
|
|
|
34
34
|
|
|
35
35
|
# Detect available JSON adapters
|
|
36
36
|
def self.detect_adapter
|
|
37
|
-
return :standard if defined?(::JSON) && Lutaml::Model
|
|
37
|
+
return :standard if defined?(::JSON) && Lutaml::Model.opal?
|
|
38
38
|
return :oj if defined?(::Oj)
|
|
39
39
|
return :multi_json if defined?(::MultiJson)
|
|
40
40
|
return :standard if defined?(::JSON)
|
|
@@ -249,8 +249,9 @@ format)
|
|
|
249
249
|
value = apply_value_map(value, rule.value_map(:from, options), attr)
|
|
250
250
|
|
|
251
251
|
if rule.has_custom_method_for_deserialization?
|
|
252
|
-
return
|
|
253
|
-
|
|
252
|
+
return unless Lutaml::Model::Utils.present?(value)
|
|
253
|
+
|
|
254
|
+
return rule.deserialize(instance, value, attributes, model_class)
|
|
254
255
|
end
|
|
255
256
|
|
|
256
257
|
value = rule.transform_value(attr, value, :from, format)
|
|
@@ -265,12 +266,6 @@ format)
|
|
|
265
266
|
rule.deserialize(instance, value, attributes, self)
|
|
266
267
|
end
|
|
267
268
|
|
|
268
|
-
def process_custom_method(rule, instance, value)
|
|
269
|
-
return unless Lutaml::Model::Utils.present?(value)
|
|
270
|
-
|
|
271
|
-
model_class.new.public_send(rule.custom_methods[:from], instance, value)
|
|
272
|
-
end
|
|
273
|
-
|
|
274
269
|
def cast_value(value, attr, format, rule, instance)
|
|
275
270
|
cast_options = rule.polymorphic ? { polymorphic: rule.polymorphic } : {}
|
|
276
271
|
cast_options[:lutaml_parent] = instance if instance
|
|
@@ -63,13 +63,26 @@ model_class: nil)
|
|
|
63
63
|
return serialize_reference(value, rule)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
if
|
|
66
|
+
if Lutaml::Model::Type::Union.rule?(rule)
|
|
67
|
+
serialize_union(value, options)
|
|
68
|
+
elsif nested_model?(rule)
|
|
67
69
|
serialize_nested_model(value, rule, options)
|
|
68
70
|
else
|
|
69
71
|
serialize_primitive(value, rule)
|
|
70
72
|
end
|
|
71
73
|
end
|
|
72
74
|
|
|
75
|
+
# Union: dispatch on the value's own class (stateless). A model member
|
|
76
|
+
# serializes via its class transformation; a scalar emits as-is.
|
|
77
|
+
def serialize_union(value, options)
|
|
78
|
+
unless value.is_a?(Lutaml::Model::Serialize)
|
|
79
|
+
return Lutaml::Model::Type::Union.serialize_scalar(value, format)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
transform_nested_model(value, create_transformation(value.class),
|
|
83
|
+
options)
|
|
84
|
+
end
|
|
85
|
+
|
|
73
86
|
# Check if a value is a nested model based on the rule.
|
|
74
87
|
#
|
|
75
88
|
# @param rule [CompiledRule] The compiled rule
|
|
@@ -936,9 +936,15 @@ child_mappings, options)
|
|
|
936
936
|
def create_value_for_item(rule, value, options)
|
|
937
937
|
return nil if value.nil?
|
|
938
938
|
|
|
939
|
-
#
|
|
940
|
-
|
|
941
|
-
|
|
939
|
+
# Union: dispatch on the value's own class (stateless, like polymorphism).
|
|
940
|
+
# A model member serializes via its class; a scalar member emits as-is.
|
|
941
|
+
union = Lutaml::Model::Type::Union.rule?(rule)
|
|
942
|
+
is_nested_model = if union
|
|
943
|
+
value.is_a?(Lutaml::Model::Serialize)
|
|
944
|
+
else
|
|
945
|
+
rule.attribute_type.is_a?(Class) &&
|
|
946
|
+
rule.attribute_type < Lutaml::Model::Serialize
|
|
947
|
+
end
|
|
942
948
|
|
|
943
949
|
if is_nested_model
|
|
944
950
|
# Validate that value matches the expected type
|
|
@@ -970,8 +976,9 @@ child_mappings, options)
|
|
|
970
976
|
# If value is a subclass of the declared type, use its mappings instead
|
|
971
977
|
# Also handle type substitution the same way
|
|
972
978
|
actual_type = value.class
|
|
973
|
-
uses_polymorphism =
|
|
974
|
-
actual_type
|
|
979
|
+
uses_polymorphism = union ||
|
|
980
|
+
(actual_type != rule.attribute_type &&
|
|
981
|
+
actual_type < rule.attribute_type) || uses_type_substitution
|
|
975
982
|
|
|
976
983
|
# Get child transformation - may be cached or need to create
|
|
977
984
|
child_transformation = rule.child_transformation
|
|
@@ -998,6 +1005,11 @@ child_mappings, options)
|
|
|
998
1005
|
# Fallback: serialize as primitive
|
|
999
1006
|
serialize_value(value, rule)
|
|
1000
1007
|
end
|
|
1008
|
+
elsif union
|
|
1009
|
+
# Union scalar: serialize through the value type matching the value's
|
|
1010
|
+
# own class so output is canonical (e.g. BigDecimal -> "1.5"), not a
|
|
1011
|
+
# raw Ruby object.
|
|
1012
|
+
Lutaml::Model::Type::Union.serialize_scalar(value, format)
|
|
1001
1013
|
else
|
|
1002
1014
|
# Serialize primitive value
|
|
1003
1015
|
serialize_value(value, rule)
|
|
@@ -231,7 +231,7 @@ module Lutaml
|
|
|
231
231
|
end
|
|
232
232
|
|
|
233
233
|
# Default key-value adapter loading
|
|
234
|
-
adapter_path = if
|
|
234
|
+
adapter_path = if Lutaml::Model.opal?
|
|
235
235
|
"lutaml/key_value/adapter/#{adapter}/#{type}"
|
|
236
236
|
else
|
|
237
237
|
File.join(File.dirname(__FILE__), "../key_value/adapter",
|
|
@@ -382,7 +382,7 @@ module Lutaml
|
|
|
382
382
|
#
|
|
383
383
|
# @return [Symbol, nil] :tomlib, :toml_rb, or nil
|
|
384
384
|
def detect_toml_adapter
|
|
385
|
-
return nil if
|
|
385
|
+
return nil if Lutaml::Model.opal?
|
|
386
386
|
|
|
387
387
|
if RuntimeCompatibility.windows?
|
|
388
388
|
return :toml_rb if Utils.safe_load("toml-rb", :TomlRb)
|
|
@@ -28,6 +28,7 @@ module Lutaml
|
|
|
28
28
|
ref_model_class
|
|
29
29
|
ref_key_attribute
|
|
30
30
|
xsd_type
|
|
31
|
+
union_member_types
|
|
31
32
|
].freeze
|
|
32
33
|
|
|
33
34
|
MODEL_STRINGS = [
|
|
@@ -239,6 +240,15 @@ module Lutaml
|
|
|
239
240
|
@options[:polymorphic_class]
|
|
240
241
|
end
|
|
241
242
|
|
|
243
|
+
def union?
|
|
244
|
+
unresolved_type == Lutaml::Model::Type::Union
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def union_member_types
|
|
248
|
+
@union_member_types ||=
|
|
249
|
+
@options[:union_member_types].map { |member| cast_type!(member) }
|
|
250
|
+
end
|
|
251
|
+
|
|
242
252
|
def derived?
|
|
243
253
|
!method_name.nil?
|
|
244
254
|
end
|
|
@@ -276,6 +286,43 @@ module Lutaml
|
|
|
276
286
|
build_collection(value.map { |v| cast_element(v, register) })
|
|
277
287
|
end
|
|
278
288
|
|
|
289
|
+
# Apply a value map to transform a value.
|
|
290
|
+
#
|
|
291
|
+
# value_map keys (:nil, :empty, :omitted) each map either to a symbolic
|
|
292
|
+
# option (:nil, :empty, :omitted) or, for Boolean attributes, directly
|
|
293
|
+
# to true/false. The nested form value_map[:from][key] = true/false is
|
|
294
|
+
# accepted unconditionally; the bare form value_map[key] = true/false
|
|
295
|
+
# is gated on the attribute being a Boolean type.
|
|
296
|
+
def apply_value_map(value, value_map)
|
|
297
|
+
key = if value.nil? then :nil
|
|
298
|
+
elsif Utils.empty?(value) then :empty
|
|
299
|
+
elsif Utils.uninitialized?(value) then :omitted
|
|
300
|
+
end
|
|
301
|
+
return value unless key
|
|
302
|
+
|
|
303
|
+
nested = value_map.dig(:from, key)
|
|
304
|
+
return nested if nested.is_a?(TrueClass) || nested.is_a?(FalseClass)
|
|
305
|
+
|
|
306
|
+
option = value_map[key]
|
|
307
|
+
if (option.is_a?(TrueClass) || option.is_a?(FalseClass)) &&
|
|
308
|
+
(type == Lutaml::Model::Type::Boolean ||
|
|
309
|
+
unresolved_type == Lutaml::Model::Type::Boolean)
|
|
310
|
+
return option
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
case option
|
|
314
|
+
when :nil
|
|
315
|
+
nil
|
|
316
|
+
when :empty
|
|
317
|
+
if key == :empty then value
|
|
318
|
+
elsif collection? then build_collection
|
|
319
|
+
else ""
|
|
320
|
+
end
|
|
321
|
+
else
|
|
322
|
+
Lutaml::Model::UninitializedClass.instance
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
279
326
|
def required_value_set?(value)
|
|
280
327
|
return true unless options[:required]
|
|
281
328
|
return false if value.nil?
|
|
@@ -286,6 +333,7 @@ module Lutaml
|
|
|
286
333
|
|
|
287
334
|
def cast_element(value, register)
|
|
288
335
|
resolved_type = type(register)
|
|
336
|
+
return cast_union(value, nil, register) if union?
|
|
289
337
|
return resolved_type.new(value) if value.is_a?(::Hash) && !hash_type?
|
|
290
338
|
|
|
291
339
|
# Special handling for Reference types - pass the metadata
|
|
@@ -398,12 +446,19 @@ instance_object = nil)
|
|
|
398
446
|
options[:values].include?(value)
|
|
399
447
|
end
|
|
400
448
|
|
|
449
|
+
# Pattern binds string values only. For a plain :string attribute that is
|
|
450
|
+
# the resolved type; for a union, the pattern applies to a value that took
|
|
451
|
+
# the :string branch (a non-string member is exempt). Collections check
|
|
452
|
+
# each element, so a mix of members validates only its string entries.
|
|
401
453
|
def valid_pattern!(value, resolved_type)
|
|
402
|
-
return true unless resolved_type == Lutaml::Model::Type::String
|
|
403
454
|
return true unless pattern
|
|
404
455
|
|
|
405
|
-
|
|
406
|
-
|
|
456
|
+
Array(value).each do |item|
|
|
457
|
+
next unless resolved_type == Lutaml::Model::Type::String ||
|
|
458
|
+
item.is_a?(::String)
|
|
459
|
+
next if pattern.match?(item)
|
|
460
|
+
|
|
461
|
+
raise Lutaml::Model::PatternNotMatchedError.new(name, pattern, item)
|
|
407
462
|
end
|
|
408
463
|
|
|
409
464
|
true
|
|
@@ -419,11 +474,11 @@ instance_object = nil)
|
|
|
419
474
|
# 2. Value count should be between the collection range if defined
|
|
420
475
|
# e.g if collection: 0..5 is set then the value greater then 5
|
|
421
476
|
# will raise `Lutaml::Model::CollectionCountOutOfRangeError`
|
|
422
|
-
def validate_value!(value, register,
|
|
477
|
+
def validate_value!(value, register, instance_object: nil)
|
|
423
478
|
# Use the default value if the value is nil
|
|
424
479
|
validate_required!(value)
|
|
425
480
|
|
|
426
|
-
value =
|
|
481
|
+
value = cast_value(default_value(register, instance_object), register) if value.nil?
|
|
427
482
|
resolved_type = type(register)
|
|
428
483
|
|
|
429
484
|
valid_value!(value) &&
|
|
@@ -587,6 +642,8 @@ instance_object = nil)
|
|
|
587
642
|
|
|
588
643
|
return value if already_serialized?(resolved_type, value)
|
|
589
644
|
|
|
645
|
+
return cast_union(value, format, register) if union?
|
|
646
|
+
|
|
590
647
|
# Special handling for Reference types - pass the metadata
|
|
591
648
|
# Check @options[:ref_model_class] which is set when type is { ref: [...] }
|
|
592
649
|
if @options[:ref_model_class] && resolved_type == Lutaml::Model::Type::Reference
|
|
@@ -664,6 +721,11 @@ instance_object = nil)
|
|
|
664
721
|
|
|
665
722
|
def process_options!
|
|
666
723
|
validate_options!(@options)
|
|
724
|
+
if union?
|
|
725
|
+
@options[:union_member_types] =
|
|
726
|
+
Lutaml::Model::Type::Union.validate_members!(@options[:union_member_types])
|
|
727
|
+
Lutaml::Model::Type::Union.validate_combo!(@options)
|
|
728
|
+
end
|
|
667
729
|
@raw = !!@options[:raw]
|
|
668
730
|
if @raw
|
|
669
731
|
warn "[DEPRECATED] attribute :#{name}, :string, raw: true is deprecated. " \
|
|
@@ -776,6 +838,13 @@ instance_object = nil)
|
|
|
776
838
|
|
|
777
839
|
attr_writer :raw, :validations
|
|
778
840
|
|
|
841
|
+
def cast_union(value, format, register)
|
|
842
|
+
match = Lutaml::Model::Type::Union.conforming_member(
|
|
843
|
+
value, union_member_types, format: format, register: register
|
|
844
|
+
)
|
|
845
|
+
match&.last
|
|
846
|
+
end
|
|
847
|
+
|
|
779
848
|
def validate_attr_type!(resolved_type)
|
|
780
849
|
return true if resolved_type <= Serializable || resolved_type <= Type::Value
|
|
781
850
|
return true if resolved_type.include?(Serialize)
|
|
@@ -931,12 +1000,20 @@ instance_object = nil)
|
|
|
931
1000
|
"Called from #{caller(1..1).first}"
|
|
932
1001
|
end
|
|
933
1002
|
|
|
934
|
-
#
|
|
935
|
-
#
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1003
|
+
# pattern applies to string values: a plain :string attribute, or a
|
|
1004
|
+
# union that includes a :string member (the pattern binds that branch).
|
|
1005
|
+
# Guarded by the pattern key so non-pattern attributes never resolve
|
|
1006
|
+
# their type here (custom types may be absent from the default register).
|
|
1007
|
+
if options.key?(:pattern)
|
|
1008
|
+
union_with_string =
|
|
1009
|
+
type == Lutaml::Model::Type::Union &&
|
|
1010
|
+
Array(options[:union_member_types]).intersect?(MODEL_STRINGS)
|
|
1011
|
+
unless MODEL_STRINGS.include?(type) || union_with_string
|
|
1012
|
+
raise StandardError,
|
|
1013
|
+
"Invalid option `pattern` given for `#{name}`, " \
|
|
1014
|
+
"`pattern` is only allowed for :string type or a union " \
|
|
1015
|
+
"including a :string member"
|
|
1016
|
+
end
|
|
940
1017
|
end
|
|
941
1018
|
|
|
942
1019
|
if initialize_empty? && !collection?
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Lutaml
|
|
2
|
+
module Model
|
|
3
|
+
class UnionSchemaUnsupportedError < Error
|
|
4
|
+
def initialize(attribute, format)
|
|
5
|
+
super("Union-typed attribute `#{attribute}` cannot be exported to " \
|
|
6
|
+
"#{format}; union types are only representable in JSON Schema " \
|
|
7
|
+
"(anyOf).")
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -324,10 +324,13 @@ context_id = nil)
|
|
|
324
324
|
end
|
|
325
325
|
|
|
326
326
|
class << self
|
|
327
|
-
#
|
|
328
|
-
#
|
|
327
|
+
# Block form: avoids Opal's runtime string parsing requirement
|
|
328
|
+
# (MRI's class_eval(string) needs `require 'opal-parser'` at
|
|
329
|
+
# runtime under Opal, which isn't bundled). Closures are
|
|
330
|
+
# allocated once at file load, not per call, so the perf cost
|
|
331
|
+
# over the previous string form is negligible.
|
|
329
332
|
|
|
330
|
-
class_eval
|
|
333
|
+
class_eval do
|
|
331
334
|
def registry
|
|
332
335
|
instance.registry
|
|
333
336
|
end
|
|
@@ -356,8 +359,8 @@ context_id = nil)
|
|
|
356
359
|
instance.default_context
|
|
357
360
|
end
|
|
358
361
|
|
|
359
|
-
def context(
|
|
360
|
-
instance.context(
|
|
362
|
+
def context(id = nil)
|
|
363
|
+
instance.context(id)
|
|
361
364
|
end
|
|
362
365
|
|
|
363
366
|
def clear_xml_namespace_registry!
|
|
@@ -404,8 +407,8 @@ context_id = nil)
|
|
|
404
407
|
instance.resolvable?(name, ctx)
|
|
405
408
|
end
|
|
406
409
|
|
|
407
|
-
def with_context(ctx_id)
|
|
408
|
-
instance.with_context(ctx_id)
|
|
410
|
+
def with_context(ctx_id, &block)
|
|
411
|
+
instance.with_context(ctx_id, &block)
|
|
409
412
|
end
|
|
410
413
|
|
|
411
414
|
def register_format_registry(format, registry)
|
|
@@ -440,7 +443,7 @@ context_id = nil)
|
|
|
440
443
|
def namespace_register_map
|
|
441
444
|
instance.namespace_register_map
|
|
442
445
|
end
|
|
443
|
-
|
|
446
|
+
end
|
|
444
447
|
end
|
|
445
448
|
end
|
|
446
449
|
end
|
|
@@ -155,7 +155,7 @@ module Lutaml
|
|
|
155
155
|
#
|
|
156
156
|
# @return [Float] the current monotonic time in seconds
|
|
157
157
|
def monotonic_time
|
|
158
|
-
return Time.now.to_f if Lutaml::Model
|
|
158
|
+
return Time.now.to_f if Lutaml::Model.opal?
|
|
159
159
|
|
|
160
160
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
161
161
|
end
|
|
@@ -164,7 +164,7 @@ module Lutaml
|
|
|
164
164
|
#
|
|
165
165
|
# @return [Integer, nil] memory in bytes, or nil if not available
|
|
166
166
|
def memory_usage
|
|
167
|
-
return nil if Lutaml::Model
|
|
167
|
+
return nil if Lutaml::Model.opal?
|
|
168
168
|
return nil unless defined?(GC)
|
|
169
169
|
|
|
170
170
|
GC.start if RUBY_VERSION >= "2.7"
|
|
@@ -248,12 +248,6 @@ module Lutaml
|
|
|
248
248
|
polymorphic_map && !polymorphic_map.empty?
|
|
249
249
|
end
|
|
250
250
|
|
|
251
|
-
def serialize_attribute(model, element, doc)
|
|
252
|
-
if custom_methods[:to]
|
|
253
|
-
model.public_send(custom_methods[:to], model, element, doc)
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
|
|
257
251
|
def to_value_for(model)
|
|
258
252
|
if delegate
|
|
259
253
|
model.public_send(delegate).public_send(to)
|
|
@@ -48,7 +48,7 @@ module Lutaml
|
|
|
48
48
|
# Cache for resolve_for_child results.
|
|
49
49
|
# Key: [child_class.object_id, parent_register] → Value: resolved register Symbol or nil.
|
|
50
50
|
# Classes are singletons so object_id is stable for the process lifetime.
|
|
51
|
-
RESOLVE_CACHE = if
|
|
51
|
+
RESOLVE_CACHE = if Lutaml::Model.opal?
|
|
52
52
|
{}
|
|
53
53
|
else
|
|
54
54
|
Concurrent::Map.new
|
|
@@ -82,7 +82,7 @@ module Lutaml
|
|
|
82
82
|
def self.resolve_for_child(child_class, parent_register)
|
|
83
83
|
cache_key = [child_class.object_id, parent_register]
|
|
84
84
|
|
|
85
|
-
if
|
|
85
|
+
if Lutaml::Model.opal?
|
|
86
86
|
return RESOLVE_CACHE[cache_key] if RESOLVE_CACHE.key?(cache_key)
|
|
87
87
|
|
|
88
88
|
RESOLVE_CACHE[cache_key] =
|
|
@@ -91,4 +91,54 @@ if Lutaml::Model::RuntimeCompatibility.opal?
|
|
|
91
91
|
end
|
|
92
92
|
end
|
|
93
93
|
end
|
|
94
|
+
|
|
95
|
+
# Opal has no `weakref` stdlib. Store uses WeakRef to let go of
|
|
96
|
+
# registered instances automatically once they go out of scope.
|
|
97
|
+
# Under Opal (browser JS GC) we substitute a strong reference —
|
|
98
|
+
# correct behavior, weaker memory profile, no API surface change.
|
|
99
|
+
unless defined?(WeakRef)
|
|
100
|
+
class ::WeakRef
|
|
101
|
+
class RefError < ::StandardError; end
|
|
102
|
+
|
|
103
|
+
def initialize(obj)
|
|
104
|
+
@__target__ = obj
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def __getobj__
|
|
108
|
+
@__target__
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def weakref_alive?
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Minimal method_missing so the WeakRef quacks like its target
|
|
116
|
+
# for the Store's index lookups.
|
|
117
|
+
def method_missing(name, *, &)
|
|
118
|
+
@__target__.public_send(name, *, &)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def respond_to_missing?(name, include_private = false)
|
|
122
|
+
@__target__.respond_to?(name, include_private)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Opal's Module#prepend raises "Prepending a module multiple times
|
|
128
|
+
# is not supported" when a module is already in the target's ancestor
|
|
129
|
+
# chain. MRI's prepend is idempotent in that case (no-op, no error).
|
|
130
|
+
#
|
|
131
|
+
# Our top-level lib files (lib/lutaml/model.rb, lib/lutaml/xml.rb)
|
|
132
|
+
# can be re-evaluated under Opal's eager loader, which would trigger
|
|
133
|
+
# the raise on the second pass. Align Opal with MRI by making prepend
|
|
134
|
+
# a no-op when the module is already present.
|
|
135
|
+
class ::Module
|
|
136
|
+
unless method_defined?(:__lm_original_prepend__)
|
|
137
|
+
alias_method :__lm_original_prepend__, :prepend
|
|
138
|
+
|
|
139
|
+
def prepend(mod)
|
|
140
|
+
__lm_original_prepend__(mod) unless self <= mod
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
94
144
|
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
unless RUBY_ENGINE == "opal"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "tmpdir"
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module Lutaml
|
|
9
|
+
module Model
|
|
10
|
+
module Schema
|
|
11
|
+
# Writes a CompiledOutput to a temporary directory, requires the
|
|
12
|
+
# registry, and runs `register_all` so the generated classes are
|
|
13
|
+
# loaded into a real module the caller can use immediately. Shared
|
|
14
|
+
# between XSD and RNG compilers.
|
|
15
|
+
class ClassLoader
|
|
16
|
+
DEFAULT_NAMESPACE = "GeneratedModels"
|
|
17
|
+
|
|
18
|
+
def self.load(output, registry_generator: RegistryGenerator)
|
|
19
|
+
new(output, registry_generator).load
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(output, registry_generator)
|
|
23
|
+
# ClassLoader needs a module to host the registry constant
|
|
24
|
+
# (so `register_all` has a home). If the caller wanted
|
|
25
|
+
# unwrapped class files, we still force a registry module —
|
|
26
|
+
# but we pin `source_module_namespace` to what they asked for,
|
|
27
|
+
# so per-class files keep their original wrapping.
|
|
28
|
+
@output = CompiledOutput.new(
|
|
29
|
+
entries: output.entries,
|
|
30
|
+
module_namespace: output.module_namespace || DEFAULT_NAMESPACE,
|
|
31
|
+
register_id: output.register_id,
|
|
32
|
+
source_module_namespace: output.module_namespace,
|
|
33
|
+
)
|
|
34
|
+
@registry_generator = registry_generator
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def load
|
|
38
|
+
Dir.mktmpdir do |dir|
|
|
39
|
+
FileWriter.write(@output, dir, registry_generator: @registry_generator)
|
|
40
|
+
require File.join(dir, "#{registry_basename}_registry")
|
|
41
|
+
call_register_all
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def registry_basename
|
|
48
|
+
@output.module_namespace.split("::").last.downcase
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def call_register_all
|
|
52
|
+
mod = Object.const_get(@output.module_namespace)
|
|
53
|
+
mod.register_all if mod.respond_to?(:register_all)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|