lutaml-model 0.7.1 → 0.7.7

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 (259) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/.github/workflows/dependent-tests.yml +4 -0
  4. data/.github/workflows/rake.yml +12 -0
  5. data/.github/workflows/release.yml +3 -0
  6. data/.gitignore +6 -1
  7. data/.irbrc +1 -0
  8. data/.pryrc +1 -0
  9. data/.rubocop.yml +1 -1
  10. data/.rubocop_todo.yml +58 -84
  11. data/Gemfile +4 -1
  12. data/README.adoc +3115 -365
  13. data/RELEASE_NOTES.adoc +346 -0
  14. data/docs/custom_adapters.adoc +144 -0
  15. data/docs/custom_registers.adoc +228 -0
  16. data/docs/schema_generation.adoc +898 -0
  17. data/docs/schema_import.adoc +364 -0
  18. data/flake.lock +114 -0
  19. data/flake.nix +103 -0
  20. data/lib/lutaml/model/attribute.rb +242 -100
  21. data/lib/lutaml/model/choice.rb +30 -0
  22. data/lib/lutaml/model/collection.rb +212 -0
  23. data/lib/lutaml/model/comparable_model.rb +3 -3
  24. data/lib/lutaml/model/config.rb +71 -42
  25. data/lib/lutaml/model/constants.rb +2 -0
  26. data/lib/lutaml/model/error/element_count_out_of_range_error.rb +29 -0
  27. data/lib/lutaml/model/error/invalid_attribute_name_error.rb +15 -0
  28. data/lib/lutaml/model/error/invalid_attribute_options_error.rb +16 -0
  29. data/lib/lutaml/model/error/invalid_choice_range_error.rb +3 -5
  30. data/lib/lutaml/model/error/polymorphic_error.rb +7 -2
  31. data/lib/lutaml/model/error/register/not_registrable_class_error.rb +11 -0
  32. data/lib/lutaml/model/error/type/invalid_value_error.rb +5 -3
  33. data/lib/lutaml/model/error/type/max_bound_error.rb +20 -0
  34. data/lib/lutaml/model/error/type/max_length_error.rb +20 -0
  35. data/lib/lutaml/model/error/type/min_bound_error.rb +20 -0
  36. data/lib/lutaml/model/error/type/min_length_error.rb +20 -0
  37. data/lib/lutaml/model/error/type/pattern_not_matched_error.rb +18 -0
  38. data/lib/lutaml/model/error/validation_failed_error.rb +9 -0
  39. data/lib/lutaml/model/error.rb +10 -0
  40. data/lib/lutaml/model/errors.rb +36 -0
  41. data/lib/lutaml/model/format_registry.rb +44 -0
  42. data/lib/lutaml/model/global_register.rb +41 -0
  43. data/lib/lutaml/model/hash_adapter/document.rb +11 -0
  44. data/lib/lutaml/model/hash_adapter/mapping.rb +19 -0
  45. data/lib/lutaml/model/hash_adapter/mapping_rule.rb +9 -0
  46. data/lib/lutaml/model/hash_adapter/standard_adapter.rb +17 -0
  47. data/lib/lutaml/model/hash_adapter/transform.rb +8 -0
  48. data/lib/lutaml/model/hash_adapter.rb +21 -0
  49. data/lib/lutaml/model/json/document.rb +11 -0
  50. data/lib/lutaml/model/json/mapping.rb +19 -0
  51. data/lib/lutaml/model/json/mapping_rule.rb +9 -0
  52. data/lib/lutaml/model/{json_adapter → json}/multi_json_adapter.rb +4 -5
  53. data/lib/lutaml/model/{json_adapter/standard_json_adapter.rb → json/standard_adapter.rb} +5 -3
  54. data/lib/lutaml/model/json/transform.rb +8 -0
  55. data/lib/lutaml/model/json.rb +21 -0
  56. data/lib/lutaml/model/jsonl/document.rb +14 -0
  57. data/lib/lutaml/model/jsonl/mapping.rb +19 -0
  58. data/lib/lutaml/model/jsonl/mapping_rule.rb +9 -0
  59. data/lib/lutaml/model/jsonl/standard_adapter.rb +33 -0
  60. data/lib/lutaml/model/jsonl/transform.rb +19 -0
  61. data/lib/lutaml/model/jsonl.rb +21 -0
  62. data/lib/lutaml/model/key_value_document.rb +28 -0
  63. data/lib/lutaml/model/mapping/key_value_mapping.rb +85 -13
  64. data/lib/lutaml/model/mapping/key_value_mapping_rule.rb +5 -0
  65. data/lib/lutaml/model/mapping/mapping.rb +13 -0
  66. data/lib/lutaml/model/mapping/mapping_rule.rb +15 -9
  67. data/lib/lutaml/model/register.rb +105 -0
  68. data/lib/lutaml/model/{toml_adapter.rb → registrable.rb} +1 -1
  69. data/lib/lutaml/model/schema/base_schema.rb +64 -0
  70. data/lib/lutaml/model/schema/decorators/attribute.rb +114 -0
  71. data/lib/lutaml/model/schema/decorators/choices.rb +31 -0
  72. data/lib/lutaml/model/schema/decorators/class_definition.rb +85 -0
  73. data/lib/lutaml/model/schema/decorators/definition_collection.rb +97 -0
  74. data/lib/lutaml/model/schema/generator/definition.rb +53 -0
  75. data/lib/lutaml/model/schema/generator/definitions_collection.rb +81 -0
  76. data/lib/lutaml/model/schema/generator/properties_collection.rb +63 -0
  77. data/lib/lutaml/model/schema/generator/property.rb +110 -0
  78. data/lib/lutaml/model/schema/generator/ref.rb +24 -0
  79. data/lib/lutaml/model/schema/helpers/template_helper.rb +49 -0
  80. data/lib/lutaml/model/schema/json_schema.rb +42 -49
  81. data/lib/lutaml/model/schema/relaxng_schema.rb +14 -10
  82. data/lib/lutaml/model/schema/renderer.rb +36 -0
  83. data/lib/lutaml/model/schema/shared_methods.rb +24 -0
  84. data/lib/lutaml/model/schema/templates/model.erb +9 -0
  85. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +85 -0
  86. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +45 -0
  87. data/lib/lutaml/model/schema/xml_compiler/choice.rb +65 -0
  88. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +27 -0
  89. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +34 -0
  90. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +136 -0
  91. data/lib/lutaml/model/schema/xml_compiler/element.rb +104 -0
  92. data/lib/lutaml/model/schema/xml_compiler/group.rb +97 -0
  93. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +101 -0
  94. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +50 -0
  95. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +36 -0
  96. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +189 -0
  97. data/lib/lutaml/model/schema/xml_compiler.rb +231 -587
  98. data/lib/lutaml/model/schema/xsd_schema.rb +12 -8
  99. data/lib/lutaml/model/schema/yaml_schema.rb +41 -35
  100. data/lib/lutaml/model/schema.rb +1 -0
  101. data/lib/lutaml/model/sequence.rb +60 -30
  102. data/lib/lutaml/model/serialization_adapter.rb +22 -0
  103. data/lib/lutaml/model/serialize.rb +296 -548
  104. data/lib/lutaml/model/services/base.rb +11 -0
  105. data/lib/lutaml/model/services/logger.rb +54 -0
  106. data/lib/lutaml/model/services/rule_value_extractor.rb +92 -0
  107. data/lib/lutaml/model/services/transformer.rb +48 -0
  108. data/lib/lutaml/model/services/type/validator/number.rb +25 -0
  109. data/lib/lutaml/model/services/type/validator/string.rb +52 -0
  110. data/lib/lutaml/model/services/type/validator.rb +43 -0
  111. data/lib/lutaml/model/services/validator.rb +145 -0
  112. data/lib/lutaml/model/services.rb +5 -0
  113. data/lib/lutaml/model/toml/document.rb +11 -0
  114. data/lib/lutaml/model/toml/mapping.rb +27 -0
  115. data/lib/lutaml/model/toml/mapping_rule.rb +9 -0
  116. data/lib/lutaml/model/{toml_adapter → toml}/toml_rb_adapter.rb +3 -3
  117. data/lib/lutaml/model/toml/tomlib_adapter.rb +19 -0
  118. data/lib/lutaml/model/toml/transform.rb +8 -0
  119. data/lib/lutaml/model/toml.rb +30 -0
  120. data/lib/lutaml/model/transform/key_value_transform.rb +297 -0
  121. data/lib/lutaml/model/transform/xml_transform.rb +228 -0
  122. data/lib/lutaml/model/transform.rb +93 -0
  123. data/lib/lutaml/model/type/boolean.rb +1 -1
  124. data/lib/lutaml/model/type/date.rb +1 -1
  125. data/lib/lutaml/model/type/date_time.rb +1 -1
  126. data/lib/lutaml/model/type/decimal.rb +11 -9
  127. data/lib/lutaml/model/type/float.rb +2 -1
  128. data/lib/lutaml/model/type/integer.rb +24 -21
  129. data/lib/lutaml/model/type/string.rb +4 -2
  130. data/lib/lutaml/model/type/time.rb +1 -1
  131. data/lib/lutaml/model/type/time_without_date.rb +1 -1
  132. data/lib/lutaml/model/type/value.rb +11 -10
  133. data/lib/lutaml/model/type.rb +5 -2
  134. data/lib/lutaml/model/uninitialized_class.rb +1 -1
  135. data/lib/lutaml/model/utils.rb +56 -4
  136. data/lib/lutaml/model/validation.rb +6 -4
  137. data/lib/lutaml/model/version.rb +1 -1
  138. data/lib/lutaml/model/{xml_adapter → xml}/builder/nokogiri.rb +2 -2
  139. data/lib/lutaml/model/{xml_adapter → xml}/builder/oga.rb +10 -10
  140. data/lib/lutaml/model/{xml_adapter → xml}/builder/ox.rb +1 -1
  141. data/lib/lutaml/model/{xml_adapter/xml_document.rb → xml/document.rb} +42 -25
  142. data/lib/lutaml/model/xml/element.rb +32 -0
  143. data/lib/lutaml/model/xml/mapping.rb +471 -0
  144. data/lib/lutaml/model/xml/mapping_rule.rb +149 -0
  145. data/lib/lutaml/model/xml/nokogiri_adapter.rb +234 -0
  146. data/lib/lutaml/model/{xml_adapter → xml}/oga/document.rb +1 -1
  147. data/lib/lutaml/model/{xml_adapter → xml}/oga/element.rb +7 -2
  148. data/lib/lutaml/model/xml/oga_adapter.rb +172 -0
  149. data/lib/lutaml/model/xml/ox_adapter.rb +229 -0
  150. data/lib/lutaml/model/xml/transform.rb +8 -0
  151. data/lib/lutaml/model/{xml_adapter → xml}/xml_attribute.rb +1 -1
  152. data/lib/lutaml/model/{xml_adapter → xml}/xml_element.rb +9 -31
  153. data/lib/lutaml/model/{xml_adapter → xml}/xml_namespace.rb +1 -1
  154. data/lib/lutaml/model/xml.rb +31 -0
  155. data/lib/lutaml/model/xml_adapter/element.rb +11 -25
  156. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +6 -223
  157. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +13 -163
  158. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +10 -207
  159. data/lib/lutaml/model/yaml/document.rb +10 -0
  160. data/lib/lutaml/model/yaml/mapping.rb +19 -0
  161. data/lib/lutaml/model/yaml/mapping_rule.rb +9 -0
  162. data/lib/lutaml/model/{yaml_adapter/standard_yaml_adapter.rb → yaml/standard_adapter.rb} +4 -3
  163. data/lib/lutaml/model/yaml/transform.rb +8 -0
  164. data/lib/lutaml/model/yaml.rb +21 -0
  165. data/lib/lutaml/model/yamls/document.rb +14 -0
  166. data/lib/lutaml/model/yamls/mapping.rb +19 -0
  167. data/lib/lutaml/model/yamls/mapping_rule.rb +9 -0
  168. data/lib/lutaml/model/yamls/standard_adapter.rb +34 -0
  169. data/lib/lutaml/model/yamls/transform.rb +19 -0
  170. data/lib/lutaml/model/yamls.rb +21 -0
  171. data/lib/lutaml/model.rb +15 -4
  172. data/lutaml-model.gemspec +0 -4
  173. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +5 -6
  174. data/spec/fixtures/xml/advanced_test_schema.xsd +134 -0
  175. data/spec/fixtures/xml/examples/nested_categories.xml +55 -0
  176. data/spec/fixtures/xml/examples/valid_catalog.xml +43 -0
  177. data/spec/fixtures/xml/product_catalog.xsd +151 -0
  178. data/spec/fixtures/xml/specifications_schema.xsd +38 -0
  179. data/spec/lutaml/model/attribute_collection_spec.rb +101 -0
  180. data/spec/lutaml/model/attribute_spec.rb +41 -44
  181. data/spec/lutaml/model/cdata_spec.rb +7 -7
  182. data/spec/lutaml/model/choice_spec.rb +44 -0
  183. data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +598 -0
  184. data/spec/lutaml/model/custom_collection_spec.rb +830 -0
  185. data/spec/lutaml/model/custom_vobject_adapter_spec.rb +1226 -0
  186. data/spec/lutaml/model/global_register_spec.rb +108 -0
  187. data/spec/lutaml/model/group_spec.rb +27 -10
  188. data/spec/lutaml/model/hash/adapter_spec.rb +255 -0
  189. data/spec/lutaml/model/json_adapter_spec.rb +6 -6
  190. data/spec/lutaml/model/jsonl/standard_adapter_spec.rb +91 -0
  191. data/spec/lutaml/model/jsonl_spec.rb +229 -0
  192. data/spec/lutaml/model/key_value_mapping_spec.rb +25 -1
  193. data/spec/lutaml/model/mixed_content_spec.rb +24 -24
  194. data/spec/lutaml/model/multiple_mapping_spec.rb +6 -6
  195. data/spec/lutaml/model/ordered_content_spec.rb +6 -6
  196. data/spec/lutaml/model/polymorphic_spec.rb +178 -0
  197. data/spec/lutaml/model/register/key_value_spec.rb +275 -0
  198. data/spec/lutaml/model/register/xml_spec.rb +185 -0
  199. data/spec/lutaml/model/register_spec.rb +147 -0
  200. data/spec/lutaml/model/root_mappings_spec.rb +3 -3
  201. data/spec/lutaml/model/rule_value_extractor_spec.rb +162 -0
  202. data/spec/lutaml/model/schema/generator/definitions_collection_spec.rb +120 -0
  203. data/spec/lutaml/model/schema/json_schema_spec.rb +412 -51
  204. data/spec/lutaml/model/schema/json_schema_to_models_spec.rb +383 -0
  205. data/spec/lutaml/model/schema/xml_compiler/attribute_group_spec.rb +65 -0
  206. data/spec/lutaml/model/schema/xml_compiler/attribute_spec.rb +63 -0
  207. data/spec/lutaml/model/schema/xml_compiler/choice_spec.rb +71 -0
  208. data/spec/lutaml/model/schema/xml_compiler/complex_content_restriction_spec.rb +55 -0
  209. data/spec/lutaml/model/schema/xml_compiler/complex_content_spec.rb +37 -0
  210. data/spec/lutaml/model/schema/xml_compiler/complex_type_spec.rb +173 -0
  211. data/spec/lutaml/model/schema/xml_compiler/element_spec.rb +63 -0
  212. data/spec/lutaml/model/schema/xml_compiler/group_spec.rb +86 -0
  213. data/spec/lutaml/model/schema/xml_compiler/restriction_spec.rb +76 -0
  214. data/spec/lutaml/model/schema/xml_compiler/sequence_spec.rb +59 -0
  215. data/spec/lutaml/model/schema/xml_compiler/simple_content_spec.rb +55 -0
  216. data/spec/lutaml/model/schema/xml_compiler/simple_type_spec.rb +181 -0
  217. data/spec/lutaml/model/schema/xml_compiler_spec.rb +503 -1804
  218. data/spec/lutaml/model/schema/yaml_schema_spec.rb +249 -26
  219. data/spec/lutaml/model/sequence_spec.rb +36 -0
  220. data/spec/lutaml/model/serializable_spec.rb +201 -94
  221. data/spec/lutaml/model/toml_adapter_spec.rb +6 -6
  222. data/spec/lutaml/model/toml_spec.rb +51 -0
  223. data/spec/lutaml/model/transformation_spec.rb +72 -15
  224. data/spec/lutaml/model/type_spec.rb +8 -4
  225. data/spec/lutaml/model/uninitialized_class_spec.rb +96 -0
  226. data/spec/lutaml/model/utils_spec.rb +3 -3
  227. data/spec/lutaml/model/xml/derived_attributes_spec.rb +1 -1
  228. data/spec/lutaml/model/xml/namespace_spec.rb +57 -0
  229. data/spec/lutaml/model/xml/root_mappings/nested_child_mappings_spec.rb +217 -0
  230. data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
  231. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +2 -2
  232. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +2 -2
  233. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +2 -2
  234. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +12 -12
  235. data/spec/lutaml/model/xml_adapter_spec.rb +30 -6
  236. data/spec/lutaml/model/xml_mapping_rule_spec.rb +14 -7
  237. data/spec/lutaml/model/xml_mapping_spec.rb +27 -15
  238. data/spec/lutaml/model/xml_spec.rb +63 -0
  239. data/spec/lutaml/model/yaml_adapter_spec.rb +3 -5
  240. data/spec/lutaml/model/yamls/standard_adapter_spec.rb +183 -0
  241. data/spec/lutaml/model/yamls_spec.rb +294 -0
  242. data/spec/spec_helper.rb +4 -3
  243. metadata +162 -60
  244. data/lib/lutaml/model/json_adapter/json_document.rb +0 -20
  245. data/lib/lutaml/model/json_adapter/json_object.rb +0 -28
  246. data/lib/lutaml/model/loggable.rb +0 -15
  247. data/lib/lutaml/model/mapping/json_mapping.rb +0 -17
  248. data/lib/lutaml/model/mapping/toml_mapping.rb +0 -25
  249. data/lib/lutaml/model/mapping/xml_mapping.rb +0 -389
  250. data/lib/lutaml/model/mapping/xml_mapping_rule.rb +0 -139
  251. data/lib/lutaml/model/mapping/yaml_mapping.rb +0 -17
  252. data/lib/lutaml/model/mapping.rb +0 -14
  253. data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
  254. data/lib/lutaml/model/toml_adapter/toml_document.rb +0 -20
  255. data/lib/lutaml/model/toml_adapter/toml_object.rb +0 -28
  256. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +0 -20
  257. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +0 -20
  258. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +0 -28
  259. data/lib/lutaml/model/yaml_adapter.rb +0 -8
@@ -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
@@ -96,6 +96,48 @@ module SerializeableSpec
96
96
  }
97
97
  end
98
98
  end
99
+
100
+ ### Single option serialization
101
+
102
+ class SingleOptionModel < Lutaml::Model::Serializable
103
+ attribute :name, :string
104
+ attribute :age, :integer
105
+ attribute :phone, :string
106
+ attribute :address, :string
107
+
108
+ json do
109
+ map "name", to: :name
110
+ map "age", to: :age
111
+ map "phone", to: :phone, with: { to: :phone_to_json }
112
+ map "address", to: :address, with: { from: :address_from_json }
113
+ end
114
+
115
+ xml do
116
+ root "person"
117
+ map_element "name", to: :name
118
+ map_element "age", to: :age
119
+ map_element "phone", to: :phone, with: { to: :phone_to_xml }
120
+ map_element "address", to: :address, with: { from: :address_from_xml }
121
+ end
122
+
123
+ def phone_to_json(model, doc)
124
+ doc["phone"] = "+1-#{model.phone}"
125
+ end
126
+
127
+ def address_from_json(model, value)
128
+ model.address = value.sub(/^Address: /, "")
129
+ end
130
+
131
+ def phone_to_xml(model, parent, doc)
132
+ el = doc.create_element("phone")
133
+ doc.add_text(el, "+1-#{model.phone}")
134
+ doc.add_element(parent, el)
135
+ end
136
+
137
+ def address_from_xml(model, value)
138
+ model.address = value.text.sub(/^Address: /, "")
139
+ end
140
+ end
99
141
  end
100
142
 
101
143
  RSpec.describe Lutaml::Model::Serializable do
@@ -151,45 +193,34 @@ RSpec.describe Lutaml::Model::Serializable do
151
193
  end
152
194
  end
153
195
 
154
- describe ".hash_representation" do
155
- context "when model is separate" do
156
- let(:instance) do
157
- SerializeableSpec::TestModel.new(name: "John", age: 18)
158
- end
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
159
201
 
160
- let(:expected_hash) do
161
- {
162
- "name" => "John",
163
- "age" => "18",
164
- }
165
- end
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)
166
206
 
167
- it "return hash representation" do
168
- generate_hash = SerializeableSpec::TestModelMapper.hash_representation(
169
- instance, :yaml
170
- )
171
- expect(generate_hash).to eq(expected_hash)
172
- end
207
+ expect(RestrictTestClass.attributes[:foo].options[:values]).to eq([1, 2, 3])
173
208
  end
174
209
 
175
- context "when model is self" do
176
- let(:instance) do
177
- SerializeableSpec::TestMapper.new(name: "John", age: 18)
178
- end
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
179
215
 
180
- let(:expected_hash) do
181
- {
182
- "na" => "John",
183
- "ag" => "18",
184
- }
185
- end
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
186
220
 
187
- it "return hash representation" do
188
- generate_hash = SerializeableSpec::TestMapper.hash_representation(
189
- instance, :yaml
190
- )
191
- expect(generate_hash).to eq(expected_hash)
192
- end
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)
193
224
  end
194
225
  end
195
226
 
@@ -221,67 +252,68 @@ RSpec.describe Lutaml::Model::Serializable do
221
252
  end
222
253
  end
223
254
 
224
- describe ".translate_mappings" do
225
- let(:child_mappings) do
226
- {
227
- id: :key,
228
- path: %i[path link],
229
- name: %i[path name],
230
- }
231
- end
232
-
233
- let(:hash) do
234
- {
235
- "foo" => {
236
- "path" => {
237
- "link" => "link one",
238
- "name" => "one",
239
- },
240
- },
241
- "abc" => {
242
- "path" => {
243
- "link" => "link two",
244
- "name" => "two",
245
- },
246
- },
247
- "hello" => {
248
- "path" => {
249
- "link" => "link three",
250
- "name" => "three",
251
- },
252
- },
253
- }
254
- end
255
-
256
- let(:attr) { SerializeableSpec::TranslateMappings.attributes[:translate] }
257
-
258
- let(:expected_value) do
259
- [
260
- SerializeableSpec::TranslateHelper.new({
261
- "id" => "foo",
262
- "name" => "one",
263
- "path" => "link one",
264
- }),
265
- SerializeableSpec::TranslateHelper.new({
266
- "id" => "abc",
267
- "name" => "two",
268
- "path" => "link two",
269
- }),
270
- SerializeableSpec::TranslateHelper.new({
271
- "id" => "hello",
272
- "name" => "three",
273
- "path" => "link three",
274
- }),
275
- ]
276
- end
277
-
278
- it "generates hash based on child_mappings" do
279
- actual_value = described_class.translate_mappings(hash, child_mappings, attr, :yaml)
280
-
281
- expect(actual_value.map { |obj| [obj.id, obj.name, obj.path] })
282
- .to eq(expected_value.map { |obj| [obj.id, obj.name, obj.path] })
283
- end
284
- end
255
+ # TODO: Move to key_value_transform specs
256
+ # describe ".translate_mappings" do
257
+ # let(:child_mappings) do
258
+ # {
259
+ # id: :key,
260
+ # path: %i[path link],
261
+ # name: %i[path name],
262
+ # }
263
+ # end
264
+
265
+ # let(:hash) do
266
+ # {
267
+ # "foo" => {
268
+ # "path" => {
269
+ # "link" => "link one",
270
+ # "name" => "one",
271
+ # },
272
+ # },
273
+ # "abc" => {
274
+ # "path" => {
275
+ # "link" => "link two",
276
+ # "name" => "two",
277
+ # },
278
+ # },
279
+ # "hello" => {
280
+ # "path" => {
281
+ # "link" => "link three",
282
+ # "name" => "three",
283
+ # },
284
+ # },
285
+ # }
286
+ # end
287
+
288
+ # let(:attr) { SerializeableSpec::TranslateMappings.attributes[:translate] }
289
+
290
+ # let(:expected_value) do
291
+ # [
292
+ # SerializeableSpec::TranslateHelper.new({
293
+ # "id" => "foo",
294
+ # "name" => "one",
295
+ # "path" => "link one",
296
+ # }),
297
+ # SerializeableSpec::TranslateHelper.new({
298
+ # "id" => "abc",
299
+ # "name" => "two",
300
+ # "path" => "link two",
301
+ # }),
302
+ # SerializeableSpec::TranslateHelper.new({
303
+ # "id" => "hello",
304
+ # "name" => "three",
305
+ # "path" => "link three",
306
+ # }),
307
+ # ]
308
+ # end
309
+
310
+ # it "generates hash based on child_mappings" do
311
+ # actual_value = described_class.translate_mappings(hash, child_mappings, attr, :yaml)
312
+
313
+ # expect(actual_value.map { |obj| [obj.id, obj.name, obj.path] })
314
+ # .to eq(expected_value.map { |obj| [obj.id, obj.name, obj.path] })
315
+ # end
316
+ # end
285
317
 
286
318
  describe "#key_value" do
287
319
  let(:model) { SerializeableSpec::KeyValueMapper }
@@ -386,4 +418,79 @@ RSpec.describe Lutaml::Model::Serializable do
386
418
  end
387
419
  end
388
420
  end
421
+
422
+ describe "Single option serialization" do
423
+ let(:attributes) do
424
+ {
425
+ name: "John Doe",
426
+ age: 30,
427
+ phone: "123-456-7890",
428
+ address: "123 Main St",
429
+ }
430
+ end
431
+
432
+ let(:model) { SerializeableSpec::SingleOptionModel.new(attributes) }
433
+
434
+ describe "JSON serialization" do
435
+ let(:expected_json) do
436
+ {
437
+ name: "John Doe",
438
+ age: 30,
439
+ phone: "+1-123-456-7890",
440
+ address: "123 Main St",
441
+ }.to_json
442
+ end
443
+
444
+ let(:parsed) do
445
+ SerializeableSpec::SingleOptionModel.from_json(
446
+ {
447
+ name: "John Doe",
448
+ age: 30,
449
+ phone: "123-456-7890",
450
+ address: "Address: 123 Main St",
451
+ }.to_json,
452
+ )
453
+ end
454
+
455
+ it "serializes to JSON with custom name transformation" do
456
+ expect(model.to_json).to eq(expected_json)
457
+ end
458
+
459
+ it "deserializes from JSON with custom name transformation" do
460
+ expect(parsed).to eq(model)
461
+ end
462
+ end
463
+
464
+ describe "XML serialization" do
465
+ let(:expected_xml) do
466
+ <<~XML
467
+ <person>
468
+ <name>John Doe</name>
469
+ <age>30</age>
470
+ <phone>+1-123-456-7890</phone>
471
+ <address>123 Main St</address>
472
+ </person>
473
+ XML
474
+ end
475
+
476
+ let(:parsed) do
477
+ SerializeableSpec::SingleOptionModel.from_xml <<~XML
478
+ <person>
479
+ <name>John Doe</name>
480
+ <age>30</age>
481
+ <phone>123-456-7890</phone>
482
+ <address>Address: 123 Main St</address>
483
+ </person>
484
+ XML
485
+ end
486
+
487
+ it "serializes to XML with custom name transformation" do
488
+ expect(model.to_xml).to be_equivalent_to(expected_xml)
489
+ end
490
+
491
+ it "deserializes from XML with custom name transformation" do
492
+ expect(parsed).to eq(model)
493
+ end
494
+ end
495
+ end
389
496
  end