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,222 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "validate_xml_with XSD constructs" do
|
|
6
|
+
def fixture_path(name)
|
|
7
|
+
File.expand_path("../../fixtures/xml/#{name}", __dir__)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe "kitchen-sink schema (pattern, enumeration, attributes, " \
|
|
11
|
+
"types, occurrence, nested complex type)" do
|
|
12
|
+
before do
|
|
13
|
+
path = fixture_path("validate_xml_with_product.xsd")
|
|
14
|
+
|
|
15
|
+
stub_const("XsdDimensions", Class.new(Lutaml::Model::Serializable) do
|
|
16
|
+
attribute :width, :string
|
|
17
|
+
attribute :height, :string
|
|
18
|
+
|
|
19
|
+
xml do
|
|
20
|
+
element "dimensions"
|
|
21
|
+
map_element "width", to: :width
|
|
22
|
+
map_element "height", to: :height
|
|
23
|
+
end
|
|
24
|
+
end)
|
|
25
|
+
|
|
26
|
+
stub_const("XsdProduct", Class.new(Lutaml::Model::Serializable) do
|
|
27
|
+
attribute :id, :string
|
|
28
|
+
attribute :status, :string
|
|
29
|
+
attribute :sku, :string
|
|
30
|
+
attribute :price, :string
|
|
31
|
+
attribute :released, :string
|
|
32
|
+
attribute :tags, :string, collection: true
|
|
33
|
+
attribute :note, :string
|
|
34
|
+
attribute :dimensions, XsdDimensions
|
|
35
|
+
|
|
36
|
+
xml do
|
|
37
|
+
element "product"
|
|
38
|
+
map_attribute "id", to: :id
|
|
39
|
+
map_attribute "status", to: :status
|
|
40
|
+
map_element "sku", to: :sku
|
|
41
|
+
map_element "price", to: :price
|
|
42
|
+
map_element "released", to: :released
|
|
43
|
+
map_element "tag", to: :tags
|
|
44
|
+
map_element "note", to: :note
|
|
45
|
+
map_element "dimensions", to: :dimensions
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
validate_xml_with path
|
|
49
|
+
end)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
let(:valid_attributes) do
|
|
53
|
+
{
|
|
54
|
+
id: "p1",
|
|
55
|
+
status: "published",
|
|
56
|
+
sku: "ABC-1234",
|
|
57
|
+
price: "9.99",
|
|
58
|
+
released: "2026-06-11",
|
|
59
|
+
tags: %w[a b c],
|
|
60
|
+
note: "boundary case: three tags is maxOccurs",
|
|
61
|
+
dimensions: XsdDimensions.new(width: "10", height: "20"),
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def product(**overrides)
|
|
66
|
+
XsdProduct.new(valid_attributes.merge(overrides))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "accepts a document satisfying every construct" do
|
|
70
|
+
expect(product.validate).to be_empty
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "accepts a document omitting all optional parts" do
|
|
74
|
+
minimal = product(status: nil, tags: [], note: nil, dimensions: nil)
|
|
75
|
+
|
|
76
|
+
expect(minimal.validate).to be_empty
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "collects xs:pattern violations" do
|
|
80
|
+
errors = product(sku: "bad-sku").validate
|
|
81
|
+
|
|
82
|
+
expect(errors.map(&:message)).to include(a_string_including("sku"))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "collects xs:enumeration violations" do
|
|
86
|
+
errors = product(status: "archived").validate
|
|
87
|
+
|
|
88
|
+
expect(errors.map(&:message)).to include(a_string_including("status"))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "collects missing required attribute violations" do
|
|
92
|
+
errors = product(id: nil).validate
|
|
93
|
+
|
|
94
|
+
expect(errors.map(&:message)).to include(
|
|
95
|
+
a_string_including("attribute 'id' is required"),
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "collects xs:decimal lexical violations" do
|
|
100
|
+
errors = product(price: "free").validate
|
|
101
|
+
|
|
102
|
+
expect(errors.map(&:message)).to include(a_string_including("price"))
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "collects xs:date lexical violations" do
|
|
106
|
+
errors = product(released: "tomorrow").validate
|
|
107
|
+
|
|
108
|
+
expect(errors.map(&:message)).to include(a_string_including("released"))
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "collects maxOccurs violations" do
|
|
112
|
+
errors = product(tags: %w[a b c d]).validate
|
|
113
|
+
|
|
114
|
+
expect(errors.map(&:message)).to include(a_string_including("tag"))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "collects violations inside nested complex types" do
|
|
118
|
+
bad_nested = XsdDimensions.new(width: "wide", height: "20")
|
|
119
|
+
errors = product(dimensions: bad_nested).validate
|
|
120
|
+
|
|
121
|
+
expect(errors.map(&:message)).to include(a_string_including("width"))
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
describe "multiple schema paths" do
|
|
126
|
+
before do
|
|
127
|
+
base = fixture_path("validate_xml_with_person.xsd")
|
|
128
|
+
strict = fixture_path("validate_xml_with_person_strict.xsd")
|
|
129
|
+
|
|
130
|
+
stub_const("XsdDualSchemaPerson",
|
|
131
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
132
|
+
attribute :name, :string
|
|
133
|
+
attribute :age, :string
|
|
134
|
+
|
|
135
|
+
xml do
|
|
136
|
+
element "person"
|
|
137
|
+
map_element "name", to: :name
|
|
138
|
+
map_element "age", to: :age
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
validate_xml_with base, strict
|
|
142
|
+
end)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it "passes when the document conforms to every schema" do
|
|
146
|
+
person = XsdDualSchemaPerson.new(name: "Alice", age: "30")
|
|
147
|
+
|
|
148
|
+
expect(person.validate).to be_empty
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it "attributes violations to the schema that rejected the document" do
|
|
152
|
+
person = XsdDualSchemaPerson.new(name: "Old", age: "200")
|
|
153
|
+
errors = person.validate
|
|
154
|
+
|
|
155
|
+
expect(errors.size).to eq(1)
|
|
156
|
+
expect(errors.first.schema_path).to eq(
|
|
157
|
+
fixture_path("validate_xml_with_person_strict.xsd"),
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
describe "targetNamespace with qualified elements" do
|
|
163
|
+
let(:schema_path) { fixture_path("validate_xml_with_address.xsd") }
|
|
164
|
+
|
|
165
|
+
before do
|
|
166
|
+
path = schema_path
|
|
167
|
+
stub_const("XsdNamespacedDoc",
|
|
168
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
169
|
+
attribute :city, :string
|
|
170
|
+
|
|
171
|
+
xml do
|
|
172
|
+
element "address"
|
|
173
|
+
map_element "city", to: :city
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
validate_xml_with path
|
|
177
|
+
end)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it "accepts a document in the target namespace" do
|
|
181
|
+
xml = '<address xmlns="http://example.com/address">' \
|
|
182
|
+
"<city>Berlin</city></address>"
|
|
183
|
+
|
|
184
|
+
expect(XsdNamespacedDoc.validate_xml(xml)).to be_empty
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it "rejects a document missing the target namespace" do
|
|
188
|
+
xml = "<address><city>Berlin</city></address>"
|
|
189
|
+
|
|
190
|
+
expect(XsdNamespacedDoc.validate_xml(xml)).not_to be_empty
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
describe "xs:choice" do
|
|
195
|
+
before do
|
|
196
|
+
path = fixture_path("validate_xml_with_contact.xsd")
|
|
197
|
+
stub_const("XsdChoiceDoc",
|
|
198
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
199
|
+
attribute :email, :string
|
|
200
|
+
|
|
201
|
+
xml do
|
|
202
|
+
element "contact"
|
|
203
|
+
map_element "email", to: :email
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
validate_xml_with path
|
|
207
|
+
end)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it "accepts a document using exactly one branch" do
|
|
211
|
+
xml = "<contact><email>a@b.c</email></contact>"
|
|
212
|
+
|
|
213
|
+
expect(XsdChoiceDoc.validate_xml(xml)).to be_empty
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
it "rejects a document using both branches" do
|
|
217
|
+
xml = "<contact><email>a@b.c</email><phone>123</phone></contact>"
|
|
218
|
+
|
|
219
|
+
expect(XsdChoiceDoc.validate_xml(xml)).not_to be_empty
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
|
|
6
|
+
RSpec.describe "validate_xml_with" do
|
|
7
|
+
let(:schema_path) do
|
|
8
|
+
File.expand_path("../../fixtures/xml/validate_xml_with_person.xsd",
|
|
9
|
+
__dir__)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
path = schema_path
|
|
14
|
+
stub_const("XsdValidatedPerson",
|
|
15
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
16
|
+
attribute :name, :string
|
|
17
|
+
attribute :age, :string
|
|
18
|
+
|
|
19
|
+
xml do
|
|
20
|
+
element "person"
|
|
21
|
+
map_element "name", to: :name
|
|
22
|
+
map_element "age", to: :age
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
validate_xml_with path
|
|
26
|
+
end)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context "when the generated XML conforms to the schema" do
|
|
30
|
+
let(:person) { XsdValidatedPerson.new(name: "Alice", age: "30") }
|
|
31
|
+
|
|
32
|
+
it "returns no errors from validate" do
|
|
33
|
+
expect(person.validate).to be_empty
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "does not raise on validate!" do
|
|
37
|
+
expect { person.validate! }.not_to raise_error
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context "when the generated XML violates the schema" do
|
|
42
|
+
let(:person) { XsdValidatedPerson.new(name: "Alice", age: "abc") }
|
|
43
|
+
|
|
44
|
+
it "collects schema validation errors" do
|
|
45
|
+
errors = person.validate
|
|
46
|
+
|
|
47
|
+
expect(errors).to all(
|
|
48
|
+
be_a(Lutaml::Xml::Error::SchemaValidationError),
|
|
49
|
+
)
|
|
50
|
+
expect(errors.first.message).to include(schema_path)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "raises ValidationError with the schema message on validate!" do
|
|
54
|
+
expect { person.validate! }.to raise_error(
|
|
55
|
+
Lutaml::Model::ValidationError,
|
|
56
|
+
/age/,
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe ".validate_xml / .validate_xml!" do
|
|
62
|
+
let(:valid_xml) do
|
|
63
|
+
"<person><name>Alice</name><age>30</age></person>"
|
|
64
|
+
end
|
|
65
|
+
let(:invalid_xml) do
|
|
66
|
+
"<person><name>Alice</name><age>abc</age></person>"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "returns no errors for a conforming raw XML string" do
|
|
70
|
+
expect(XsdValidatedPerson.validate_xml(valid_xml)).to be_empty
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "collects errors for a non-conforming raw XML string" do
|
|
74
|
+
errors = XsdValidatedPerson.validate_xml(invalid_xml)
|
|
75
|
+
|
|
76
|
+
expect(errors).to all(
|
|
77
|
+
be_a(Lutaml::Xml::Error::SchemaValidationError),
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "raises ValidationError for a non-conforming raw XML string" do
|
|
82
|
+
expect do
|
|
83
|
+
XsdValidatedPerson.validate_xml!(invalid_xml)
|
|
84
|
+
end.to raise_error(Lutaml::Model::ValidationError, /age/)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "raises a parse error for malformed XML instead of recovering" do
|
|
88
|
+
expect do
|
|
89
|
+
XsdValidatedPerson.validate_xml("<person><name>Alice</name>")
|
|
90
|
+
end.to raise_error(Nokogiri::XML::SyntaxError)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context "with schema path inheritance" do
|
|
95
|
+
before do
|
|
96
|
+
stub_const("XsdValidatedEmployee", Class.new(XsdValidatedPerson))
|
|
97
|
+
stub_const("XsdAugmentedEmployee",
|
|
98
|
+
Class.new(XsdValidatedPerson) do
|
|
99
|
+
validate_xml_with "extra/employee.xsd"
|
|
100
|
+
end)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "subclasses inherit the parent's schema" do
|
|
104
|
+
employee = XsdValidatedEmployee.new(name: "Bob", age: "abc")
|
|
105
|
+
|
|
106
|
+
expect(employee.validate).not_to be_empty
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "a subclass macro call appends parent-first" do
|
|
110
|
+
expect(XsdAugmentedEmployee.xml_schema_paths).to eq(
|
|
111
|
+
[schema_path, File.expand_path("extra/employee.xsd", __dir__)],
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "does not leak child paths back to the parent" do
|
|
116
|
+
expect(XsdValidatedPerson.xml_schema_paths).to eq([schema_path])
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
context "with a schema path relative to the declaring file" do
|
|
121
|
+
before do
|
|
122
|
+
stub_const("RelativeSchemaPerson",
|
|
123
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
124
|
+
attribute :name, :string
|
|
125
|
+
attribute :age, :string
|
|
126
|
+
|
|
127
|
+
xml do
|
|
128
|
+
element "person"
|
|
129
|
+
map_element "name", to: :name
|
|
130
|
+
map_element "age", to: :age
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
validate_xml_with(
|
|
134
|
+
"../../fixtures/xml/validate_xml_with_person.xsd",
|
|
135
|
+
)
|
|
136
|
+
end)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it "resolves the schema next to the declaring file, not the CWD" do
|
|
140
|
+
Dir.chdir(Dir.tmpdir) do
|
|
141
|
+
person = RelativeSchemaPerson.new(name: "Alice", age: "abc")
|
|
142
|
+
|
|
143
|
+
expect(person.validate).not_to be_empty
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
context "when no schema is configured" do
|
|
149
|
+
before do
|
|
150
|
+
stub_const("PlainPerson", Class.new(Lutaml::Model::Serializable) do
|
|
151
|
+
attribute :name, :string
|
|
152
|
+
|
|
153
|
+
xml do
|
|
154
|
+
element "person"
|
|
155
|
+
map_element "name", to: :name
|
|
156
|
+
end
|
|
157
|
+
end)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "validates without touching XSD machinery" do
|
|
161
|
+
expect(PlainPerson.new(name: "Alice").validate).to be_empty
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
context "when the schema file does not exist" do
|
|
166
|
+
before do
|
|
167
|
+
stub_const("MissingSchemaPerson",
|
|
168
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
169
|
+
attribute :name, :string
|
|
170
|
+
|
|
171
|
+
xml do
|
|
172
|
+
element "person"
|
|
173
|
+
map_element "name", to: :name
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
validate_xml_with "nonexistent/schema.xsd"
|
|
177
|
+
end)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it "raises a configuration-level error" do
|
|
181
|
+
expect do
|
|
182
|
+
MissingSchemaPerson.new(name: "Alice").validate
|
|
183
|
+
end.to raise_error(Errno::ENOENT)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "liquid"
|
|
3
|
+
require "liquid" unless RUBY_ENGINE == "opal"
|
|
4
4
|
require "rspec/matchers"
|
|
5
|
-
require "canon"
|
|
5
|
+
require "canon" unless RUBY_ENGINE == "opal"
|
|
6
6
|
|
|
7
7
|
RSpec.configure do |config|
|
|
8
8
|
# Enable flags like --only-failures and --next-failure
|
|
@@ -71,11 +71,27 @@ RSpec.configure do |config|
|
|
|
71
71
|
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
|
+
|
|
75
|
+
# Under Opal, exclude specs that require native-only adapters,
|
|
76
|
+
# filesystem, subprocess, schema compilation, or liquid templates.
|
|
77
|
+
if RUBY_ENGINE == "opal"
|
|
78
|
+
config.filter_run_excluding(
|
|
79
|
+
:native_adapter,
|
|
80
|
+
:native_fs,
|
|
81
|
+
:subprocess,
|
|
82
|
+
:performance,
|
|
83
|
+
:examples,
|
|
84
|
+
:consistency,
|
|
85
|
+
:xsd_schema,
|
|
86
|
+
:relaxng,
|
|
87
|
+
:liquid,
|
|
88
|
+
)
|
|
89
|
+
end
|
|
74
90
|
end
|
|
75
91
|
|
|
76
92
|
# configuration example
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
require "lutaml/model"
|
|
94
|
+
require "lutaml/xml"
|
|
79
95
|
require_relative "support/test_namespaces"
|
|
80
96
|
require_relative "support/test_adapter_config"
|
|
81
97
|
# require_relative "../lib/lutaml/xml/adapter/nokogiri_adapter"
|
|
@@ -83,16 +99,17 @@ require_relative "support/test_adapter_config"
|
|
|
83
99
|
# require_relative "../lib/lutaml/model/toml_adapter/toml_rb_adapter"
|
|
84
100
|
|
|
85
101
|
Lutaml::Model::Config.configure do |config|
|
|
86
|
-
config.xml_adapter_type
|
|
102
|
+
config.xml_adapter_type = RUBY_ENGINE == "opal" ? :oga : :nokogiri
|
|
87
103
|
config.hash_adapter_type = :standard
|
|
88
104
|
config.json_adapter_type = :standard
|
|
89
105
|
config.yaml_adapter_type = :standard
|
|
90
|
-
|
|
106
|
+
# toml_rb uses native ext; not registered as a lutaml format under Opal.
|
|
107
|
+
config.toml_adapter_type = :toml_rb unless RUBY_ENGINE == "opal"
|
|
91
108
|
end
|
|
92
109
|
|
|
93
|
-
# Configure Canon for XML equivalence testing
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
110
|
+
# Configure Canon for XML equivalence testing (Canon is not loaded under Opal)
|
|
111
|
+
if defined?(Canon::Config)
|
|
112
|
+
Canon::Config.configure do |config|
|
|
113
|
+
config.xml.match.profile = :spec_friendly
|
|
114
|
+
end
|
|
98
115
|
end
|
data/spec/support/opal.rb
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Opal runtime patches for lutaml-model specs
|
|
3
|
+
# Opal runtime patches for lutaml-model specs.
|
|
4
|
+
# Oga (via moxml's vendored opal-oga fork) is the default XML adapter
|
|
5
|
+
# under Opal; REXML (bundled stdlib gem, pure Ruby) is also available.
|
|
6
|
+
# Smoke specs parameterize over both — see spec/lutaml/xml/opal_xml_spec.rb.
|
|
4
7
|
if RUBY_ENGINE == "opal"
|
|
5
|
-
Lutaml::Model::Config.xml_adapter_type = :
|
|
8
|
+
Lutaml::Model::Config.xml_adapter_type = :oga
|
|
6
9
|
end
|