lutaml-model 0.7.2 → 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 (187) 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.rb +1 -1
  117. data/lib/lutaml/model/xml_adapter/element.rb +1 -1
  118. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +1 -1
  119. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
  120. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +1 -1
  121. data/lib/lutaml/model/yamls/document.rb +14 -0
  122. data/lib/lutaml/model/yamls/mapping.rb +19 -0
  123. data/lib/lutaml/model/yamls/mapping_rule.rb +9 -0
  124. data/lib/lutaml/model/yamls/standard_adapter.rb +34 -0
  125. data/lib/lutaml/model/yamls/transform.rb +19 -0
  126. data/lib/lutaml/model/yamls.rb +21 -0
  127. data/lib/lutaml/model.rb +7 -31
  128. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +4 -5
  129. data/spec/fixtures/xml/advanced_test_schema.xsd +134 -0
  130. data/spec/fixtures/xml/examples/nested_categories.xml +55 -0
  131. data/spec/fixtures/xml/examples/valid_catalog.xml +43 -0
  132. data/spec/fixtures/xml/product_catalog.xsd +151 -0
  133. data/spec/fixtures/xml/specifications_schema.xsd +38 -0
  134. data/spec/lutaml/model/attribute_collection_spec.rb +101 -0
  135. data/spec/lutaml/model/attribute_spec.rb +41 -44
  136. data/spec/lutaml/model/choice_spec.rb +44 -0
  137. data/spec/lutaml/model/custom_collection_spec.rb +830 -0
  138. data/spec/lutaml/model/custom_model_spec.rb +15 -3
  139. data/spec/lutaml/model/custom_vobject_adapter_spec.rb +6 -6
  140. data/spec/lutaml/model/defaults_spec.rb +5 -1
  141. data/spec/lutaml/model/global_register_spec.rb +108 -0
  142. data/spec/lutaml/model/group_spec.rb +9 -3
  143. data/spec/lutaml/model/jsonl/standard_adapter_spec.rb +91 -0
  144. data/spec/lutaml/model/jsonl_spec.rb +229 -0
  145. data/spec/lutaml/model/multiple_mapping_spec.rb +1 -1
  146. data/spec/lutaml/model/register/key_value_spec.rb +275 -0
  147. data/spec/lutaml/model/register/xml_spec.rb +187 -0
  148. data/spec/lutaml/model/register_spec.rb +147 -0
  149. data/spec/lutaml/model/rule_value_extractor_spec.rb +162 -0
  150. data/spec/lutaml/model/schema/generator/definitions_collection_spec.rb +120 -0
  151. data/spec/lutaml/model/schema/json_schema_spec.rb +412 -51
  152. data/spec/lutaml/model/schema/json_schema_to_models_spec.rb +383 -0
  153. data/spec/lutaml/model/schema/xml_compiler/attribute_group_spec.rb +65 -0
  154. data/spec/lutaml/model/schema/xml_compiler/attribute_spec.rb +63 -0
  155. data/spec/lutaml/model/schema/xml_compiler/choice_spec.rb +71 -0
  156. data/spec/lutaml/model/schema/xml_compiler/complex_content_restriction_spec.rb +55 -0
  157. data/spec/lutaml/model/schema/xml_compiler/complex_content_spec.rb +37 -0
  158. data/spec/lutaml/model/schema/xml_compiler/complex_type_spec.rb +173 -0
  159. data/spec/lutaml/model/schema/xml_compiler/element_spec.rb +63 -0
  160. data/spec/lutaml/model/schema/xml_compiler/group_spec.rb +86 -0
  161. data/spec/lutaml/model/schema/xml_compiler/restriction_spec.rb +76 -0
  162. data/spec/lutaml/model/schema/xml_compiler/sequence_spec.rb +59 -0
  163. data/spec/lutaml/model/schema/xml_compiler/simple_content_spec.rb +55 -0
  164. data/spec/lutaml/model/schema/xml_compiler/simple_type_spec.rb +181 -0
  165. data/spec/lutaml/model/schema/xml_compiler_spec.rb +503 -1804
  166. data/spec/lutaml/model/schema/yaml_schema_spec.rb +249 -26
  167. data/spec/lutaml/model/sequence_spec.rb +36 -0
  168. data/spec/lutaml/model/serializable_spec.rb +31 -0
  169. data/spec/lutaml/model/type_spec.rb +8 -4
  170. data/spec/lutaml/model/utils_spec.rb +3 -3
  171. data/spec/lutaml/model/xml/derived_attributes_spec.rb +1 -1
  172. data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
  173. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
  174. data/spec/lutaml/model/xml_adapter_spec.rb +24 -0
  175. data/spec/lutaml/model/xml_mapping_rule_spec.rb +11 -4
  176. data/spec/lutaml/model/xml_mapping_spec.rb +1 -1
  177. data/spec/lutaml/model/xml_spec.rb +6 -6
  178. data/spec/lutaml/model/yamls/standard_adapter_spec.rb +183 -0
  179. data/spec/lutaml/model/yamls_spec.rb +294 -0
  180. data/spec/spec_helper.rb +1 -0
  181. metadata +105 -9
  182. data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
  183. /data/lib/lutaml/model/{hash → hash_adapter}/document.rb +0 -0
  184. /data/lib/lutaml/model/{hash → hash_adapter}/mapping.rb +0 -0
  185. /data/lib/lutaml/model/{hash → hash_adapter}/mapping_rule.rb +0 -0
  186. /data/lib/lutaml/model/{hash → hash_adapter}/standard_adapter.rb +0 -0
  187. /data/lib/lutaml/model/{hash → hash_adapter}/transform.rb +0 -0
@@ -0,0 +1,162 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Lutaml::Model::RuleValueExtractor do
4
+ subject(:extractor) do
5
+ described_class.new(rule, doc, format, attr, register, options)
6
+ end
7
+
8
+ let(:rule) { instance_double(Lutaml::Model::Jsonl::MappingRule) }
9
+ let(:doc) { { "name" => "Test", "value" => 123 } }
10
+ let(:format) { :json }
11
+ let(:attr) { instance_double(Lutaml::Model::Attribute) }
12
+ let(:register) { instance_double(Lutaml::Model::Register) }
13
+ let(:options) { {} }
14
+
15
+ describe "#call" do
16
+ context "when rule has single mapping" do
17
+ before do
18
+ allow(rule).to receive_messages(
19
+ multiple_mappings?: false,
20
+ name: "name",
21
+ root_mapping?: false,
22
+ raw_mapping?: false,
23
+ )
24
+ end
25
+
26
+ it "returns value for the rule name" do
27
+ expect(extractor.call).to eq("Test")
28
+ end
29
+ end
30
+
31
+ context "when rule has multiple mappings" do
32
+ before do
33
+ allow(rule).to receive_messages(
34
+ multiple_mappings?: true,
35
+ name: ["name", "value"],
36
+ root_mapping?: false,
37
+ raw_mapping?: false,
38
+ )
39
+ end
40
+
41
+ it "returns first initialized value" do
42
+ expect(extractor.call).to eq("Test")
43
+ end
44
+ end
45
+
46
+ context "when rule is root mapping" do
47
+ before do
48
+ allow(rule).to receive_messages(
49
+ multiple_mappings?: false,
50
+ name: "name",
51
+ root_mapping?: true,
52
+ )
53
+ end
54
+
55
+ it "returns the entire document" do
56
+ expect(extractor.call).to eq(doc)
57
+ end
58
+ end
59
+
60
+ context "when rule is raw mapping" do
61
+ before do
62
+ allow(rule).to receive_messages(
63
+ multiple_mappings?: false,
64
+ name: "name",
65
+ root_mapping?: false,
66
+ raw_mapping?: true,
67
+ )
68
+ end
69
+
70
+ it "converts document to specified format" do
71
+ expect(extractor.call).to eq('{"name":"Test","value":123}')
72
+ end
73
+ end
74
+
75
+ context "when value needs transformation" do
76
+ let(:options) do
77
+ {
78
+ key_mappings: instance_double(Lutaml::Model::KeyValueMappingRule, to_instance: :key),
79
+ value_mappings: instance_double(Lutaml::Model::KeyValueMappingRule, as_attribute: :value),
80
+ }
81
+ end
82
+
83
+ before do
84
+ allow(rule).to receive_messages(
85
+ multiple_mappings?: false,
86
+ name: "data",
87
+ root_mapping?: false,
88
+ raw_mapping?: false,
89
+ )
90
+ end
91
+
92
+ it "transforms hash values" do
93
+ doc = { "data" => { "test" => { "nested" => "value" } } }
94
+ extractor = described_class.new(rule, doc, format, attr, register, options)
95
+
96
+ expected = [{
97
+ "key" => "test",
98
+ "nested" => "value",
99
+ }]
100
+ expect(extractor.call).to eq(expected)
101
+ end
102
+
103
+ it "transforms simple values" do
104
+ doc = { "data" => { "test" => "value" } }
105
+ extractor = described_class.new(rule, doc, format, attr, register, options)
106
+
107
+ expected = [{
108
+ "key" => "test",
109
+ "value" => "value",
110
+ }]
111
+ expect(extractor.call).to eq(expected)
112
+ end
113
+ end
114
+
115
+ context "when value is not found" do
116
+ before do
117
+ allow(rule).to receive_messages(
118
+ multiple_mappings?: false,
119
+ name: "nonexistent",
120
+ root_mapping?: false,
121
+ raw_mapping?: false,
122
+ )
123
+ allow(attr).to receive(:default_set?).with(register).and_return(false)
124
+ end
125
+
126
+ it "returns uninitialized value" do
127
+ expect(extractor.call).to be_a(Lutaml::Model::UninitializedClass)
128
+ end
129
+ end
130
+
131
+ context "when attribute has default value" do
132
+ before do
133
+ allow(rule).to receive_messages(
134
+ multiple_mappings?: false,
135
+ name: "nonexistent",
136
+ root_mapping?: false,
137
+ raw_mapping?: false,
138
+ )
139
+ allow(attr).to receive(:default_set?).with(register).and_return(true)
140
+ allow(attr).to receive(:default).with(register).and_return("default_value")
141
+ end
142
+
143
+ it "returns default value" do
144
+ expect(extractor.call).to eq("default_value")
145
+ end
146
+ end
147
+ end
148
+
149
+ describe "#uninitialized_value" do
150
+ it "returns an instance of UninitializedClass" do
151
+ expect(
152
+ extractor.send(:uninitialized_value),
153
+ ).to be_a(Lutaml::Model::UninitializedClass)
154
+ end
155
+
156
+ it "returns the same instance for multiple calls" do
157
+ first_call = extractor.send(:uninitialized_value)
158
+ second_call = extractor.send(:uninitialized_value)
159
+ expect(first_call).to equal(second_call)
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "lutaml/model/schema"
5
+
6
+ module DefinitionsCollectionSpec
7
+ class Address < Lutaml::Model::Serializable
8
+ attribute :street, Lutaml::Model::Type::String
9
+ end
10
+
11
+ class HomeAddress < Address
12
+ attribute :is_home, Lutaml::Model::Type::Boolean
13
+ end
14
+
15
+ class WorkAddress < Address
16
+ attribute :company, Lutaml::Model::Type::String
17
+ end
18
+
19
+ class Person < Lutaml::Model::Serializable
20
+ attribute :name, Lutaml::Model::Type::String
21
+ attribute :age, Lutaml::Model::Type::Integer
22
+ attribute :address, Address, polymorphic: [HomeAddress, WorkAddress]
23
+ end
24
+ end
25
+
26
+ RSpec.describe Lutaml::Model::Schema::Generator::DefinitionsCollection do
27
+ let(:klass) { DefinitionsCollectionSpec::Person }
28
+ let(:register) { :def_collection }
29
+
30
+ before do
31
+ collection_register = Lutaml::Model::Register.new(register)
32
+ Lutaml::Model::GlobalRegister.register(collection_register)
33
+ end
34
+
35
+ describe ".from_class" do
36
+ subject(:collection) { described_class.from_class(klass) }
37
+
38
+ let(:all_classes) do
39
+ [
40
+ klass,
41
+ DefinitionsCollectionSpec::Address,
42
+ DefinitionsCollectionSpec::HomeAddress,
43
+ DefinitionsCollectionSpec::WorkAddress,
44
+ ]
45
+ end
46
+
47
+ it "creates a new DefinitionsCollection" do
48
+ expect(collection).to be_a(described_class)
49
+ end
50
+
51
+ it "generated definition" do
52
+ definition_class = Lutaml::Model::Schema::Generator::Definition
53
+ expect(collection.definitions.first).to be_a(definition_class)
54
+ end
55
+
56
+ it "initializes with the main class definition" do
57
+ expect(collection.definitions.size).to eq(all_classes.size)
58
+ end
59
+
60
+ it "includes definitions for main class and and all custom classes" do
61
+ definition_types = collection.definitions.map(&:type)
62
+ expect(definition_types).to include(*all_classes)
63
+ end
64
+ end
65
+
66
+ describe "#to_schema" do
67
+ subject(:schema) { collection.to_schema }
68
+
69
+ let(:collection) { described_class.from_class(klass) }
70
+
71
+ it "returns a hash" do
72
+ expect(schema).to be_a(Hash)
73
+ end
74
+
75
+ it "includes schema for all definitions" do
76
+ expect(schema.keys.size).to eq(collection.definitions.size)
77
+ end
78
+ end
79
+
80
+ describe "#add_definition" do
81
+ let(:collection) { described_class.new }
82
+ let(:definition) do
83
+ Lutaml::Model::Schema::Generator::Definition.new(String)
84
+ end
85
+
86
+ it "adds a definition to the collection" do
87
+ expect { collection.add_definition(definition) }
88
+ .to change { collection.definitions.size }.by(1)
89
+ end
90
+
91
+ it "accepts both Definition objects and classes" do
92
+ expect { collection.add_definition(String) }
93
+ .to change { collection.definitions.size }.by(1)
94
+ end
95
+ end
96
+
97
+ describe "#merge" do
98
+ let(:first_collection) do
99
+ described_class.new(
100
+ [
101
+ Lutaml::Model::Type::String,
102
+ Lutaml::Model::Type::Integer,
103
+ ],
104
+ )
105
+ end
106
+ let(:second_collection) do
107
+ described_class.new(
108
+ [
109
+ Lutaml::Model::Type::Float,
110
+ Lutaml::Model::Type::Boolean,
111
+ ],
112
+ )
113
+ end
114
+
115
+ it "merges two collections" do
116
+ first_collection.merge(second_collection)
117
+ expect(first_collection.definitions.size).to eq(4)
118
+ end
119
+ end
120
+ end