lutaml-model 0.7.3 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/.github/workflows/release.yml +3 -0
  4. data/.gitignore +6 -1
  5. data/.irbrc +1 -0
  6. data/.pryrc +1 -0
  7. data/.rubocop_todo.yml +25 -52
  8. data/README.adoc +2177 -192
  9. data/docs/custom_registers.adoc +228 -0
  10. data/docs/schema_generation.adoc +898 -0
  11. data/docs/schema_import.adoc +364 -0
  12. data/flake.lock +114 -0
  13. data/flake.nix +103 -0
  14. data/lib/lutaml/model/attribute.rb +230 -94
  15. data/lib/lutaml/model/choice.rb +30 -0
  16. data/lib/lutaml/model/collection.rb +194 -0
  17. data/lib/lutaml/model/comparable_model.rb +3 -3
  18. data/lib/lutaml/model/config.rb +26 -3
  19. data/lib/lutaml/model/constants.rb +2 -0
  20. data/lib/lutaml/model/error/element_count_out_of_range_error.rb +29 -0
  21. data/lib/lutaml/model/error/invalid_attribute_name_error.rb +15 -0
  22. data/lib/lutaml/model/error/invalid_attribute_options_error.rb +16 -0
  23. data/lib/lutaml/model/error/invalid_choice_range_error.rb +3 -5
  24. data/lib/lutaml/model/error/register/not_registrable_class_error.rb +11 -0
  25. data/lib/lutaml/model/error/type/invalid_value_error.rb +5 -3
  26. data/lib/lutaml/model/error/type/max_bound_error.rb +20 -0
  27. data/lib/lutaml/model/error/type/max_length_error.rb +20 -0
  28. data/lib/lutaml/model/error/type/min_bound_error.rb +20 -0
  29. data/lib/lutaml/model/error/type/min_length_error.rb +20 -0
  30. data/lib/lutaml/model/error/type/pattern_not_matched_error.rb +18 -0
  31. data/lib/lutaml/model/error/validation_failed_error.rb +9 -0
  32. data/lib/lutaml/model/error.rb +10 -0
  33. data/lib/lutaml/model/errors.rb +36 -0
  34. data/lib/lutaml/model/format_registry.rb +5 -2
  35. data/lib/lutaml/model/global_register.rb +41 -0
  36. data/lib/lutaml/model/{hash.rb → hash_adapter.rb} +5 -5
  37. data/lib/lutaml/model/jsonl/document.rb +14 -0
  38. data/lib/lutaml/model/jsonl/mapping.rb +19 -0
  39. data/lib/lutaml/model/jsonl/mapping_rule.rb +9 -0
  40. data/lib/lutaml/model/jsonl/standard_adapter.rb +33 -0
  41. data/lib/lutaml/model/jsonl/transform.rb +19 -0
  42. data/lib/lutaml/model/jsonl.rb +21 -0
  43. data/lib/lutaml/model/key_value_document.rb +3 -2
  44. data/lib/lutaml/model/mapping/key_value_mapping.rb +64 -4
  45. data/lib/lutaml/model/mapping/key_value_mapping_rule.rb +4 -0
  46. data/lib/lutaml/model/mapping/mapping_rule.rb +8 -3
  47. data/lib/lutaml/model/register.rb +105 -0
  48. data/lib/lutaml/model/registrable.rb +6 -0
  49. data/lib/lutaml/model/schema/base_schema.rb +64 -0
  50. data/lib/lutaml/model/schema/decorators/attribute.rb +114 -0
  51. data/lib/lutaml/model/schema/decorators/choices.rb +31 -0
  52. data/lib/lutaml/model/schema/decorators/class_definition.rb +85 -0
  53. data/lib/lutaml/model/schema/decorators/definition_collection.rb +97 -0
  54. data/lib/lutaml/model/schema/generator/definition.rb +53 -0
  55. data/lib/lutaml/model/schema/generator/definitions_collection.rb +81 -0
  56. data/lib/lutaml/model/schema/generator/properties_collection.rb +63 -0
  57. data/lib/lutaml/model/schema/generator/property.rb +110 -0
  58. data/lib/lutaml/model/schema/generator/ref.rb +24 -0
  59. data/lib/lutaml/model/schema/helpers/template_helper.rb +49 -0
  60. data/lib/lutaml/model/schema/json_schema.rb +42 -49
  61. data/lib/lutaml/model/schema/relaxng_schema.rb +14 -10
  62. data/lib/lutaml/model/schema/renderer.rb +36 -0
  63. data/lib/lutaml/model/schema/shared_methods.rb +24 -0
  64. data/lib/lutaml/model/schema/templates/model.erb +9 -0
  65. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +85 -0
  66. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +45 -0
  67. data/lib/lutaml/model/schema/xml_compiler/choice.rb +65 -0
  68. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +27 -0
  69. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +34 -0
  70. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +136 -0
  71. data/lib/lutaml/model/schema/xml_compiler/element.rb +104 -0
  72. data/lib/lutaml/model/schema/xml_compiler/group.rb +97 -0
  73. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +101 -0
  74. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +50 -0
  75. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +36 -0
  76. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +189 -0
  77. data/lib/lutaml/model/schema/xml_compiler.rb +231 -587
  78. data/lib/lutaml/model/schema/xsd_schema.rb +12 -8
  79. data/lib/lutaml/model/schema/yaml_schema.rb +41 -35
  80. data/lib/lutaml/model/schema.rb +1 -0
  81. data/lib/lutaml/model/sequence.rb +60 -30
  82. data/lib/lutaml/model/serialize.rb +175 -53
  83. data/lib/lutaml/model/services/base.rb +11 -0
  84. data/lib/lutaml/model/services/logger.rb +2 -2
  85. data/lib/lutaml/model/services/rule_value_extractor.rb +92 -0
  86. data/lib/lutaml/model/services/type/validator/number.rb +25 -0
  87. data/lib/lutaml/model/services/type/validator/string.rb +52 -0
  88. data/lib/lutaml/model/services/type/validator.rb +43 -0
  89. data/lib/lutaml/model/services/validator.rb +145 -0
  90. data/lib/lutaml/model/services.rb +3 -0
  91. data/lib/lutaml/model/transform/key_value_transform.rb +60 -50
  92. data/lib/lutaml/model/transform/xml_transform.rb +46 -57
  93. data/lib/lutaml/model/transform.rb +22 -8
  94. data/lib/lutaml/model/type/boolean.rb +1 -1
  95. data/lib/lutaml/model/type/date.rb +1 -1
  96. data/lib/lutaml/model/type/date_time.rb +1 -1
  97. data/lib/lutaml/model/type/decimal.rb +11 -9
  98. data/lib/lutaml/model/type/float.rb +2 -1
  99. data/lib/lutaml/model/type/integer.rb +24 -21
  100. data/lib/lutaml/model/type/string.rb +4 -2
  101. data/lib/lutaml/model/type/time.rb +1 -1
  102. data/lib/lutaml/model/type/time_without_date.rb +1 -1
  103. data/lib/lutaml/model/type/value.rb +5 -1
  104. data/lib/lutaml/model/type.rb +5 -2
  105. data/lib/lutaml/model/utils.rb +30 -8
  106. data/lib/lutaml/model/validation.rb +6 -4
  107. data/lib/lutaml/model/version.rb +1 -1
  108. data/lib/lutaml/model/xml/document.rb +37 -19
  109. data/lib/lutaml/model/xml/mapping.rb +74 -13
  110. data/lib/lutaml/model/xml/mapping_rule.rb +10 -2
  111. data/lib/lutaml/model/xml/nokogiri_adapter.rb +5 -3
  112. data/lib/lutaml/model/xml/oga/element.rb +4 -1
  113. data/lib/lutaml/model/xml/oga_adapter.rb +4 -3
  114. data/lib/lutaml/model/xml/ox_adapter.rb +20 -6
  115. data/lib/lutaml/model/xml/xml_element.rb +3 -28
  116. data/lib/lutaml/model/xml_adapter/element.rb +1 -1
  117. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +1 -1
  118. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
  119. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +1 -1
  120. data/lib/lutaml/model/yamls/document.rb +14 -0
  121. data/lib/lutaml/model/yamls/mapping.rb +19 -0
  122. data/lib/lutaml/model/yamls/mapping_rule.rb +9 -0
  123. data/lib/lutaml/model/yamls/standard_adapter.rb +34 -0
  124. data/lib/lutaml/model/yamls/transform.rb +19 -0
  125. data/lib/lutaml/model/yamls.rb +21 -0
  126. data/lib/lutaml/model.rb +7 -31
  127. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +4 -5
  128. data/spec/fixtures/xml/advanced_test_schema.xsd +134 -0
  129. data/spec/fixtures/xml/examples/nested_categories.xml +55 -0
  130. data/spec/fixtures/xml/examples/valid_catalog.xml +43 -0
  131. data/spec/fixtures/xml/product_catalog.xsd +151 -0
  132. data/spec/fixtures/xml/specifications_schema.xsd +38 -0
  133. data/spec/lutaml/model/attribute_collection_spec.rb +101 -0
  134. data/spec/lutaml/model/attribute_spec.rb +41 -44
  135. data/spec/lutaml/model/choice_spec.rb +44 -0
  136. data/spec/lutaml/model/custom_collection_spec.rb +830 -0
  137. data/spec/lutaml/model/custom_model_spec.rb +15 -3
  138. data/spec/lutaml/model/defaults_spec.rb +5 -1
  139. data/spec/lutaml/model/global_register_spec.rb +108 -0
  140. data/spec/lutaml/model/group_spec.rb +9 -3
  141. data/spec/lutaml/model/jsonl/standard_adapter_spec.rb +91 -0
  142. data/spec/lutaml/model/jsonl_spec.rb +229 -0
  143. data/spec/lutaml/model/multiple_mapping_spec.rb +1 -1
  144. data/spec/lutaml/model/register/key_value_spec.rb +275 -0
  145. data/spec/lutaml/model/register/xml_spec.rb +187 -0
  146. data/spec/lutaml/model/register_spec.rb +147 -0
  147. data/spec/lutaml/model/rule_value_extractor_spec.rb +162 -0
  148. data/spec/lutaml/model/schema/generator/definitions_collection_spec.rb +120 -0
  149. data/spec/lutaml/model/schema/json_schema_spec.rb +412 -51
  150. data/spec/lutaml/model/schema/json_schema_to_models_spec.rb +383 -0
  151. data/spec/lutaml/model/schema/xml_compiler/attribute_group_spec.rb +65 -0
  152. data/spec/lutaml/model/schema/xml_compiler/attribute_spec.rb +63 -0
  153. data/spec/lutaml/model/schema/xml_compiler/choice_spec.rb +71 -0
  154. data/spec/lutaml/model/schema/xml_compiler/complex_content_restriction_spec.rb +55 -0
  155. data/spec/lutaml/model/schema/xml_compiler/complex_content_spec.rb +37 -0
  156. data/spec/lutaml/model/schema/xml_compiler/complex_type_spec.rb +173 -0
  157. data/spec/lutaml/model/schema/xml_compiler/element_spec.rb +63 -0
  158. data/spec/lutaml/model/schema/xml_compiler/group_spec.rb +86 -0
  159. data/spec/lutaml/model/schema/xml_compiler/restriction_spec.rb +76 -0
  160. data/spec/lutaml/model/schema/xml_compiler/sequence_spec.rb +59 -0
  161. data/spec/lutaml/model/schema/xml_compiler/simple_content_spec.rb +55 -0
  162. data/spec/lutaml/model/schema/xml_compiler/simple_type_spec.rb +181 -0
  163. data/spec/lutaml/model/schema/xml_compiler_spec.rb +503 -1804
  164. data/spec/lutaml/model/schema/yaml_schema_spec.rb +249 -26
  165. data/spec/lutaml/model/sequence_spec.rb +36 -0
  166. data/spec/lutaml/model/serializable_spec.rb +31 -0
  167. data/spec/lutaml/model/type_spec.rb +8 -4
  168. data/spec/lutaml/model/utils_spec.rb +3 -3
  169. data/spec/lutaml/model/xml/derived_attributes_spec.rb +1 -1
  170. data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
  171. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
  172. data/spec/lutaml/model/xml_adapter_spec.rb +24 -0
  173. data/spec/lutaml/model/xml_mapping_rule_spec.rb +11 -4
  174. data/spec/lutaml/model/xml_mapping_spec.rb +1 -1
  175. data/spec/lutaml/model/yamls/standard_adapter_spec.rb +183 -0
  176. data/spec/lutaml/model/yamls_spec.rb +294 -0
  177. data/spec/spec_helper.rb +1 -0
  178. metadata +105 -9
  179. data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
  180. /data/lib/lutaml/model/{hash → hash_adapter}/document.rb +0 -0
  181. /data/lib/lutaml/model/{hash → hash_adapter}/mapping.rb +0 -0
  182. /data/lib/lutaml/model/{hash → hash_adapter}/mapping_rule.rb +0 -0
  183. /data/lib/lutaml/model/{hash → hash_adapter}/standard_adapter.rb +0 -0
  184. /data/lib/lutaml/model/{hash → hash_adapter}/transform.rb +0 -0
@@ -1,7 +1,7 @@
1
1
  require "spec_helper"
2
2
  require "lutaml/model/schema"
3
3
 
4
- module SchemaGeneration
4
+ module YamlSchemaSpec
5
5
  class Glaze < Lutaml::Model::Serializable
6
6
  attribute :color, Lutaml::Model::Type::String
7
7
  attribute :finish, Lutaml::Model::Type::String
@@ -13,35 +13,258 @@ module SchemaGeneration
13
13
  attribute :glaze, Glaze
14
14
  attribute :materials, Lutaml::Model::Type::String, collection: true
15
15
  end
16
+
17
+ class ChoiceModel < Lutaml::Model::Serializable
18
+ attribute :name, Lutaml::Model::Type::String
19
+ attribute :email, Lutaml::Model::Type::String
20
+ attribute :phone, Lutaml::Model::Type::String
21
+
22
+ choice(min: 1, max: 2) do
23
+ attribute :email, Lutaml::Model::Type::String
24
+ attribute :phone, Lutaml::Model::Type::String
25
+ end
26
+ end
27
+
28
+ class ValidationModel < Lutaml::Model::Serializable
29
+ attribute :name, Lutaml::Model::Type::String, values: %w[Alice Bob Charlie]
30
+ attribute :email, Lutaml::Model::Type::String, pattern: /.*?\S+@.+\.\S+/
31
+ attribute :age, Lutaml::Model::Type::Integer, collection: 1..3
32
+ attribute :score, Lutaml::Model::Type::Float, default: 0.0
33
+ end
34
+
35
+ class Shape < Lutaml::Model::Serializable
36
+ attribute :area, :float
37
+ end
38
+
39
+ class Circle < Shape
40
+ attribute :radius, Lutaml::Model::Type::Float
41
+ end
42
+
43
+ class Square < Shape
44
+ attribute :side, Lutaml::Model::Type::Float
45
+ end
46
+
47
+ class PolymorphicModel < Lutaml::Model::Serializable
48
+ attribute :shape, Shape, polymorphic: [Circle, Square]
49
+ end
16
50
  end
17
51
 
18
52
  RSpec.describe Lutaml::Model::Schema::YamlSchema do
19
53
  describe ".generate" do
20
- it "generates a YAML schema for nested Serialize objects" do
21
- schema = described_class.generate(SchemaGeneration::Vase)
22
-
23
- expected_schema = <<~YAML
24
- ---
25
- type: map
26
- mapping:
27
- height:
28
- type: float
29
- diameter:
30
- type: float
31
- glaze:
32
- type: map
33
- mapping:
34
- color:
35
- type: str
36
- finish:
37
- type: str
38
- materials:
39
- type: seq
40
- sequence:
41
- - type: str
42
- YAML
43
-
44
- expect(schema).to eq(expected_schema)
54
+ context "with basic model" do
55
+ let(:schema) do
56
+ described_class.generate(
57
+ YamlSchemaSpec::Vase,
58
+ id: "http://stsci.edu/schemas/yaml-schema/draft-01",
59
+ description: "A vase schema",
60
+ )
61
+ end
62
+
63
+ let(:expected_schema) do
64
+ <<~YAML
65
+ %YAML 1.1
66
+ ---
67
+ "$schema": https://json-schema.org/draft/2020-12/schema
68
+ "$id": http://stsci.edu/schemas/yaml-schema/draft-01
69
+ description: A vase schema
70
+ "$ref": "#/$defs/YamlSchemaSpec_Vase"
71
+ "$defs":
72
+ YamlSchemaSpec_Vase:
73
+ type: object
74
+ additionalProperties: false
75
+ properties:
76
+ height:
77
+ type:
78
+ - number
79
+ - 'null'
80
+ diameter:
81
+ type:
82
+ - number
83
+ - 'null'
84
+ glaze:
85
+ "$ref": "#/$defs/YamlSchemaSpec_Glaze"
86
+ materials:
87
+ type: array
88
+ items:
89
+ type: string
90
+ YamlSchemaSpec_Glaze:
91
+ type: object
92
+ additionalProperties: false
93
+ properties:
94
+ color:
95
+ type:
96
+ - string
97
+ - 'null'
98
+ finish:
99
+ type:
100
+ - string
101
+ - 'null'
102
+
103
+ YAML
104
+ end
105
+
106
+ it "generates a YAML schema for nested Serialize objects" do
107
+ expect(schema).to eq(expected_schema)
108
+ end
109
+ end
110
+
111
+ context "with choice validation" do
112
+ let(:schema) do
113
+ described_class.generate(YamlSchemaSpec::ChoiceModel)
114
+ end
115
+
116
+ let(:expected_schema) do
117
+ <<~YAML
118
+ %YAML 1.1
119
+ ---
120
+ "$schema": https://json-schema.org/draft/2020-12/schema
121
+ "$ref": "#/$defs/YamlSchemaSpec_ChoiceModel"
122
+ "$defs":
123
+ YamlSchemaSpec_ChoiceModel:
124
+ type: object
125
+ additionalProperties: false
126
+ properties:
127
+ name:
128
+ type:
129
+ - string
130
+ - 'null'
131
+ email:
132
+ type:
133
+ - string
134
+ - 'null'
135
+ phone:
136
+ type:
137
+ - string
138
+ - 'null'
139
+ oneOf:
140
+ - type: object
141
+ properties:
142
+ email:
143
+ type:
144
+ - string
145
+ - 'null'
146
+ phone:
147
+ type:
148
+ - string
149
+ - 'null'
150
+ YAML
151
+ end
152
+
153
+ it "generates a YAML schema with choice constraints" do
154
+ expect(schema.strip).to eq(expected_schema.strip)
155
+ end
156
+ end
157
+
158
+ context "with validation constraints" do
159
+ let(:schema) do
160
+ described_class.generate(YamlSchemaSpec::ValidationModel)
161
+ end
162
+
163
+ let(:expected_schema) do
164
+ <<~YAML
165
+ %YAML 1.1
166
+ ---
167
+ "$schema": https://json-schema.org/draft/2020-12/schema
168
+ "$ref": "#/$defs/YamlSchemaSpec_ValidationModel"
169
+ "$defs":
170
+ YamlSchemaSpec_ValidationModel:
171
+ type: object
172
+ additionalProperties: false
173
+ properties:
174
+ name:
175
+ type:
176
+ - string
177
+ - 'null'
178
+ enum:
179
+ - Alice
180
+ - Bob
181
+ - Charlie
182
+ email:
183
+ type:
184
+ - string
185
+ - 'null'
186
+ pattern: ".*?\\\\S+@.+\\\\.\\\\S+"
187
+ age:
188
+ type: array
189
+ items:
190
+ type: integer
191
+ minItems: 1
192
+ maxItems: 3
193
+ score:
194
+ type:
195
+ - number
196
+ - 'null'
197
+ default: 0.0
198
+ YAML
199
+ end
200
+
201
+ it "generates a YAML schema with validation constraints" do
202
+ expect(schema.strip).to eq(expected_schema.strip)
203
+ end
204
+ end
205
+
206
+ context "with polymorphic types" do
207
+ let(:schema) do
208
+ described_class.generate(YamlSchemaSpec::PolymorphicModel)
209
+ end
210
+
211
+ let(:expected_schema) do
212
+ <<~YAML
213
+ %YAML 1.1
214
+ ---
215
+ "$schema": https://json-schema.org/draft/2020-12/schema
216
+ "$ref": "#/$defs/YamlSchemaSpec_PolymorphicModel"
217
+ "$defs":
218
+ YamlSchemaSpec_PolymorphicModel:
219
+ type: object
220
+ additionalProperties: false
221
+ properties:
222
+ shape:
223
+ type:
224
+ - object
225
+ - 'null'
226
+ oneOf:
227
+ - "$ref": "#/$defs/YamlSchemaSpec_Circle"
228
+ - "$ref": "#/$defs/YamlSchemaSpec_Square"
229
+ - "$ref": "#/$defs/YamlSchemaSpec_Shape"
230
+ YamlSchemaSpec_Shape:
231
+ type: object
232
+ additionalProperties: false
233
+ properties:
234
+ area:
235
+ type:
236
+ - number
237
+ - 'null'
238
+ YamlSchemaSpec_Circle:
239
+ type: object
240
+ additionalProperties: false
241
+ properties:
242
+ area:
243
+ type:
244
+ - number
245
+ - 'null'
246
+ radius:
247
+ type:
248
+ - number
249
+ - 'null'
250
+ YamlSchemaSpec_Square:
251
+ type: object
252
+ additionalProperties: false
253
+ properties:
254
+ area:
255
+ type:
256
+ - number
257
+ - 'null'
258
+ side:
259
+ type:
260
+ - number
261
+ - 'null'
262
+ YAML
263
+ end
264
+
265
+ it "generates a YAML schema with polymorphic type constraints" do
266
+ expect(schema.strip).to eq(expected_schema.strip)
267
+ end
45
268
  end
46
269
  end
47
270
  end
@@ -50,6 +50,17 @@ module SequenceSpec
50
50
  map_element "ceramic", to: :ceramic
51
51
  end
52
52
  end
53
+
54
+ class CeramicRestricted < Ceramic
55
+ restrict :id, collection: 0..1
56
+ restrict :name, collection: 0..1
57
+ restrict :type, collection: 0..1
58
+ restrict :color, collection: 0..1
59
+ restrict :bold, collection: 0..1
60
+ restrict :text, collection: 0..1
61
+ restrict :usage, collection: 0..1
62
+ restrict :size, collection: 1..2
63
+ end
53
64
  end
54
65
 
55
66
  RSpec.describe "Sequence" do
@@ -229,4 +240,29 @@ RSpec.describe "Sequence" do
229
240
  end.to raise_error(Lutaml::Model::UnknownSequenceMappingError, "map_attribute is not allowed in sequence")
230
241
  end
231
242
  end
243
+
244
+ describe "#validate_content!" do
245
+ let(:sequence) { SequenceSpec::CeramicRestricted.mappings_for(:xml).element_sequence[0] }
246
+ let(:klass) { SequenceSpec::CeramicRestricted }
247
+
248
+ it "does not raise for correct order" do
249
+ expect { sequence.validate_content!(["tag", "id", "name", "type", "color", "bold", "text", "usage", "size"], klass) }
250
+ .not_to raise_error
251
+ end
252
+
253
+ it "raises for incorrect order" do
254
+ expect { sequence.validate_content!(["tag", "name", "id", "type", "color", "bold", "text", "usage", "size"], klass) }
255
+ .to raise_error(Lutaml::Model::IncorrectSequenceError)
256
+ end
257
+
258
+ it "raises for unknown tag" do
259
+ expect { sequence.validate_content!(["tag", "id", "name", "foo", "type", "color", "bold", "text", "usage", "size"], klass) }
260
+ .to raise_error(Lutaml::Model::IncorrectSequenceError)
261
+ end
262
+
263
+ it "raises error for missing required tag" do
264
+ expect { sequence.validate_content!(["tag", "id", "name", "type", "color", "bold", "text", "usage"], klass) }
265
+ .to raise_error(Lutaml::Model::ElementCountOutOfRangeError)
266
+ end
267
+ end
232
268
  end
@@ -193,6 +193,37 @@ RSpec.describe Lutaml::Model::Serializable do
193
193
  end
194
194
  end
195
195
 
196
+ describe ".restrict" do
197
+ before do
198
+ stub_const("RestrictTestClass", Class.new(described_class))
199
+ RestrictTestClass.attribute(:foo, :string, collection: 1..3, values: [1, 2, 3])
200
+ end
201
+
202
+ it "merges new options into the attribute's options" do
203
+ expect { RestrictTestClass.restrict(:foo, collection: 2..4) }
204
+ .to change { RestrictTestClass.attributes[:foo].options[:collection] }
205
+ .from(1..3).to(2..4)
206
+
207
+ expect(RestrictTestClass.attributes[:foo].options[:values]).to eq([1, 2, 3])
208
+ end
209
+
210
+ it "does not remove existing options not specified in restrict" do
211
+ RestrictTestClass.restrict(:foo, collection: 5..6, values: [4, 5, 6])
212
+ expect(RestrictTestClass.attributes[:foo].options[:collection]).to eq(5..6)
213
+ expect(RestrictTestClass.attributes[:foo].options[:values]).to eq([4, 5, 6])
214
+ end
215
+
216
+ it "raises an error for invalid options" do
217
+ expect { RestrictTestClass.restrict(:foo, new_option: :bar) }
218
+ .to raise_error(Lutaml::Model::InvalidAttributeOptionsError, "Invalid options given for `foo` [:new_option]")
219
+ end
220
+
221
+ it "raises an error if the attribute does not exist" do
222
+ expect { RestrictTestClass.restrict(:bar, collection: 1..2) }
223
+ .to raise_error(NoMethodError)
224
+ end
225
+ end
226
+
196
227
  describe ".mappings_for" do
197
228
  context "when mapping is defined" do
198
229
  it "returns the defined mapping" do
@@ -198,7 +198,7 @@ RSpec.describe Lutaml::Model::Type do
198
198
 
199
199
  it "raises TypeNotEnabledError when using Decimal type" do
200
200
  expect do
201
- described_class.lookup(:decimal).cast("123.45")
201
+ decimal_class.cast("123.45")
202
202
  end.to raise_error(Lutaml::Model::TypeNotEnabledError)
203
203
  end
204
204
  end
@@ -342,11 +342,14 @@ RSpec.describe Lutaml::Model::Type do
342
342
  expected_xml = <<~XML
343
343
  <sample custom_type="to_xml_overrided"/>
344
344
  XML
345
- expect(sample_instance_attribute.to_xml).to be_equivalent_to(expected_xml)
345
+ expect(
346
+ sample_instance_attribute.to_xml,
347
+ ).to be_equivalent_to(expected_xml)
346
348
  end
347
349
 
348
350
  it "correctly serializes to JSON" do
349
- expect(sample_instance.to_json).to eq('{"custom_type":"to_json_overrided"}')
351
+ expected_value = '{"custom_type":"to_json_overrided"}'
352
+ expect(sample_instance.to_json).to eq(expected_value)
350
353
  end
351
354
 
352
355
  it "correctly deserializes from XML" do
@@ -354,7 +357,8 @@ RSpec.describe Lutaml::Model::Type do
354
357
  end
355
358
 
356
359
  it "correctly deserializes from JSON" do
357
- json_sample_instance = SampleModel.from_json('{"custom_type":"test_string"}')
360
+ json_input = '{"custom_type":"test_string"}'
361
+ json_sample_instance = SampleModel.from_json(json_input)
358
362
  json_sample_instance.to_json
359
363
  expect(json_sample_instance.custom_type).to eq("from_json_overrided")
360
364
  end
@@ -37,9 +37,9 @@ RSpec.describe Lutaml::Model::Utils do
37
37
  "HelloWorld::FooBarBaz" => "hello_world/foo_bar_baz",
38
38
  }
39
39
 
40
- include_examples "string conversion", :camel_case, camel_case_examples
41
- include_examples "string conversion", :classify, classify_examples
42
- include_examples "string conversion", :snake_case, snake_case_examples
40
+ it_behaves_like "string conversion", :camel_case, camel_case_examples
41
+ it_behaves_like "string conversion", :classify, classify_examples
42
+ it_behaves_like "string conversion", :snake_case, snake_case_examples
43
43
 
44
44
  describe ".deep_dup" do
45
45
  let(:original_hash) do
@@ -5,7 +5,7 @@ require "spec_helper"
5
5
  module DerivedAttributesSpecs
6
6
  class Ceramic < Lutaml::Model::Serializable
7
7
  attribute :name, :string
8
- attribute :value, :float
8
+ attribute :value, register(:float)
9
9
  end
10
10
 
11
11
  class CeramicCollection < Lutaml::Model::Serializable
@@ -88,6 +88,12 @@ RSpec.describe "XmlElement" do
88
88
  end
89
89
 
90
90
  def create_element(name, attributes: {}, children: [], text: "")
91
- Lutaml::Model::Xml::XmlElement.new(name, attributes, children, text)
91
+ Lutaml::Model::Xml::XmlElement.new(
92
+ name,
93
+ attributes,
94
+ children,
95
+ text,
96
+ name: name,
97
+ )
92
98
  end
93
99
  end
@@ -164,15 +164,15 @@ RSpec.describe "XmlNamespace" do
164
164
  end
165
165
 
166
166
  context "with no prefix" do
167
- include_examples "XML serialization with namespace",
168
- TestModelNoPrefix,
169
- '<test xmlns="http://example.com/test"><name>Test Name</name></test>'
167
+ it_behaves_like "XML serialization with namespace",
168
+ TestModelNoPrefix,
169
+ '<test xmlns="http://example.com/test"><name>Test Name</name></test>'
170
170
  end
171
171
 
172
172
  context "with prefix" do
173
- include_examples "XML serialization with namespace",
174
- TestModelWithPrefix,
175
- '<test:test xmlns:test="http://example.com/test"><test:name>Test Name</test:name></test:test>'
173
+ it_behaves_like "XML serialization with namespace",
174
+ TestModelWithPrefix,
175
+ '<test:test xmlns:test="http://example.com/test"><test:name>Test Name</test:name></test:test>'
176
176
  end
177
177
 
178
178
  context "with prefixed namespace" do
@@ -130,6 +130,30 @@ RSpec.describe "XmlAdapter" do
130
130
  end
131
131
  end
132
132
 
133
+ context "when xml has `lang` attribute" do
134
+ before do
135
+ title_class = Class.new(Lutaml::Model::Serializable) do
136
+ attribute :lang, :string
137
+ attribute :content, :string
138
+
139
+ xml do
140
+ root "title"
141
+
142
+ map_attribute "lang", to: :lang
143
+ map_content to: :content
144
+ end
145
+ end
146
+
147
+ stub_const("Title", title_class)
148
+ end
149
+
150
+ let(:xml_input) { "<title lang='en'>Title</title>" }
151
+ let(:parsed) { Title.from_xml(xml_input) }
152
+
153
+ it { expect(parsed.lang).to eq("en") }
154
+ it { expect(parsed.content).to eq("Title") }
155
+ end
156
+
133
157
  it "serializes to XML" do
134
158
  expected_xml = <<~XML
135
159
  <SampleModel>
@@ -19,12 +19,19 @@ RSpec.describe Lutaml::Model::Xml::MappingRule do
19
19
  end
20
20
 
21
21
  context "when attribute name is string 'lang'" do
22
- let(:mapping_rule) do
23
- described_class.new("lang", to: :lang, prefix: "xml")
22
+ it "returns `xml:lang` when prefix is set" do
23
+ mapping_rule = described_class.new("lang", to: :lang, prefix: "xml")
24
+ expect(mapping_rule.namespaced_name).to eq("xml:lang")
24
25
  end
25
26
 
26
- it "returns `xml:lang`" do
27
- expect(namespaced_name).to eq("xml:lang")
27
+ it "returns `lang` when prefix is empty string" do
28
+ mapping_rule = described_class.new("lang", to: :lang, prefix: "")
29
+ expect(mapping_rule.namespaced_name).to eq("lang")
30
+ end
31
+
32
+ it "returns `lang` when prefix is nil" do
33
+ mapping_rule = described_class.new("lang", to: :lang, prefix: nil)
34
+ expect(mapping_rule.namespaced_name).to eq("lang")
28
35
  end
29
36
  end
30
37
 
@@ -732,7 +732,7 @@ RSpec.describe Lutaml::Model::Xml::Mapping do
732
732
  end
733
733
 
734
734
  it "prints warning if defined explicitly in class" do
735
- error_regex = /\[Lutaml::Model\] WARN: `schemaLocation` is handled by default\. No need to explecitly define at `xml_mapping_spec.rb:\d+`/
735
+ error_regex = /\[Lutaml::Model\] WARN: `schemaLocation` is handled by default\. No need to explicitly define at `xml_mapping_spec.rb:\d+`/
736
736
 
737
737
  expect do
738
738
  mapping.map_attribute("schemaLocation", to: :schema_location)