lutaml-model 0.3.24 → 0.3.25

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +35 -16
  3. data/README.adoc +274 -28
  4. data/lib/lutaml/model/attribute.rb +18 -8
  5. data/lib/lutaml/model/error/type_error.rb +9 -0
  6. data/lib/lutaml/model/error/unknown_type_error.rb +9 -0
  7. data/lib/lutaml/model/error/validation_error.rb +0 -1
  8. data/lib/lutaml/model/error.rb +2 -0
  9. data/lib/lutaml/model/serialize.rb +6 -1
  10. data/lib/lutaml/model/type/boolean.rb +38 -0
  11. data/lib/lutaml/model/type/date.rb +35 -0
  12. data/lib/lutaml/model/type/date_time.rb +32 -4
  13. data/lib/lutaml/model/type/decimal.rb +42 -0
  14. data/lib/lutaml/model/type/float.rb +37 -0
  15. data/lib/lutaml/model/type/hash.rb +62 -0
  16. data/lib/lutaml/model/type/integer.rb +41 -0
  17. data/lib/lutaml/model/type/string.rb +49 -0
  18. data/lib/lutaml/model/type/time.rb +49 -0
  19. data/lib/lutaml/model/type/time_without_date.rb +37 -5
  20. data/lib/lutaml/model/type/value.rb +52 -0
  21. data/lib/lutaml/model/type.rb +50 -114
  22. data/lib/lutaml/model/version.rb +1 -1
  23. data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +5 -2
  24. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +2 -1
  25. data/lib/lutaml/model/xml_adapter/xml_document.rb +0 -2
  26. data/lutaml-model.gemspec +1 -1
  27. data/spec/address_spec.rb +170 -0
  28. data/spec/fixtures/address.rb +33 -0
  29. data/spec/fixtures/person.rb +73 -0
  30. data/spec/fixtures/sample_model.rb +40 -0
  31. data/spec/fixtures/vase.rb +38 -0
  32. data/spec/fixtures/xml/special_char.xml +13 -0
  33. data/spec/lutaml/model/attribute_spec.rb +112 -0
  34. data/spec/lutaml/model/collection_spec.rb +299 -0
  35. data/spec/lutaml/model/comparable_model_spec.rb +106 -0
  36. data/spec/lutaml/model/custom_model_spec.rb +410 -0
  37. data/spec/lutaml/model/custom_serialization_spec.rb +170 -0
  38. data/spec/lutaml/model/defaults_spec.rb +221 -0
  39. data/spec/lutaml/model/delegation_spec.rb +340 -0
  40. data/spec/lutaml/model/inheritance_spec.rb +92 -0
  41. data/spec/lutaml/model/json_adapter_spec.rb +37 -0
  42. data/spec/lutaml/model/key_value_mapping_spec.rb +86 -0
  43. data/spec/lutaml/model/map_content_spec.rb +118 -0
  44. data/spec/lutaml/model/mixed_content_spec.rb +625 -0
  45. data/spec/lutaml/model/namespace_spec.rb +57 -0
  46. data/spec/lutaml/model/ordered_content_spec.rb +83 -0
  47. data/spec/lutaml/model/render_nil_spec.rb +138 -0
  48. data/spec/lutaml/model/schema/json_schema_spec.rb +79 -0
  49. data/spec/lutaml/model/schema/relaxng_schema_spec.rb +60 -0
  50. data/spec/lutaml/model/schema/xsd_schema_spec.rb +55 -0
  51. data/spec/lutaml/model/schema/yaml_schema_spec.rb +47 -0
  52. data/spec/lutaml/model/serializable_spec.rb +297 -0
  53. data/spec/lutaml/model/serializable_validation_spec.rb +85 -0
  54. data/spec/lutaml/model/simple_model_spec.rb +314 -0
  55. data/spec/lutaml/model/toml_adapter_spec.rb +39 -0
  56. data/spec/lutaml/model/type/boolean_spec.rb +54 -0
  57. data/spec/lutaml/model/type/date_spec.rb +118 -0
  58. data/spec/lutaml/model/type/date_time_spec.rb +127 -0
  59. data/spec/lutaml/model/type/decimal_spec.rb +125 -0
  60. data/spec/lutaml/model/type/float_spec.rb +191 -0
  61. data/spec/lutaml/model/type/hash_spec.rb +63 -0
  62. data/spec/lutaml/model/type/integer_spec.rb +145 -0
  63. data/spec/lutaml/model/type/string_spec.rb +150 -0
  64. data/spec/lutaml/model/type/time_spec.rb +142 -0
  65. data/spec/lutaml/model/type/time_without_date_spec.rb +125 -0
  66. data/spec/lutaml/model/type_spec.rb +276 -0
  67. data/spec/lutaml/model/utils_spec.rb +79 -0
  68. data/spec/lutaml/model/validation_spec.rb +83 -0
  69. data/spec/lutaml/model/with_child_mapping_spec.rb +174 -0
  70. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +56 -0
  71. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +56 -0
  72. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +61 -0
  73. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +251 -0
  74. data/spec/lutaml/model/xml_adapter_spec.rb +178 -0
  75. data/spec/lutaml/model/xml_mapping_spec.rb +863 -0
  76. data/spec/lutaml/model/yaml_adapter_spec.rb +30 -0
  77. data/spec/lutaml/model_spec.rb +1 -0
  78. data/spec/person_spec.rb +161 -0
  79. data/spec/spec_helper.rb +33 -0
  80. metadata +66 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18b88c11cf49c7a08199273885c202ea29ebc3aa9f470d1bffc177282b1d588b
4
- data.tar.gz: 75b4d90ea27a62c55ca6643a80358ab80a0d7636b4c6511ecafa526e0855f294
3
+ metadata.gz: 923c122ff4459f02c1949dc88ee8df94b93db6ce23c00c9da2159f6b376da3a0
4
+ data.tar.gz: aa537363d67a5383cdbd280cfa9ddee0a3c5de0cb151b79b2e195a030bc0061d
5
5
  SHA512:
6
- metadata.gz: 16d82eb3101b2e22f5d2e9197d3fdea94c787675d489a4a3792e7548f4275c6c926a87cf6a6242ee4672526ba1f0d3eb26c8fd629341240d4a93ab6d614d522f
7
- data.tar.gz: 1369a5abd8dd616e28b4a8c5b7db3369634bf37c701c0cca76b20182df922e18e120c754bb486745f7d47df0bff983bdf6cb2e3651713e08aebe5c1eeb87297f
6
+ metadata.gz: a0da4f4a87c8fe4a3951d302df5797594675f83616ea52b4582f417683d4f55076c57ad3d6f3018119c0a6021497bf6832b150ebf695d1fa8d4304da2040b641
7
+ data.tar.gz: 0d4d6d783728b53d453c23e2cfcfcd08d9eac83d94b4652d3bdee95c19d9d9bf68e233dea6d29d9478d4214271dd800be93b8f8e11a59cf9f9cd46d744dd2002
data/.rubocop_todo.yml CHANGED
@@ -1,19 +1,19 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2024-11-08 07:37:57 UTC using RuboCop version 1.66.1.
3
+ # on 2024-11-11 10:52:30 UTC using RuboCop version 1.68.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 127
9
+ # Offense count: 132
10
10
  # This cop supports safe autocorrection (--autocorrect).
11
11
  # Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
12
12
  # URISchemes: http, https
13
13
  Layout/LineLength:
14
14
  Enabled: false
15
15
 
16
- # Offense count: 11
16
+ # Offense count: 12
17
17
  # Configuration parameters: AllowedMethods.
18
18
  # AllowedMethods: enums
19
19
  Lint/ConstantDefinitionInBlock:
@@ -22,15 +22,17 @@ Lint/ConstantDefinitionInBlock:
22
22
  - 'spec/lutaml/model/schema/relaxng_schema_spec.rb'
23
23
  - 'spec/lutaml/model/schema/xsd_schema_spec.rb'
24
24
  - 'spec/lutaml/model/schema/yaml_schema_spec.rb'
25
+ - 'spec/lutaml/model/type_spec.rb'
25
26
  - 'spec/lutaml/model/validation_spec.rb'
26
27
  - 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
27
28
 
28
- # Offense count: 1
29
+ # Offense count: 2
29
30
  Lint/DuplicateMethods:
30
31
  Exclude:
31
32
  - 'lib/lutaml/model/attribute.rb'
33
+ - 'lib/lutaml/model/type/float.rb'
32
34
 
33
- # Offense count: 38
35
+ # Offense count: 36
34
36
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
35
37
  Metrics/AbcSize:
36
38
  Exclude:
@@ -40,7 +42,6 @@ Metrics/AbcSize:
40
42
  - 'lib/lutaml/model/schema/relaxng_schema.rb'
41
43
  - 'lib/lutaml/model/schema/xsd_schema.rb'
42
44
  - 'lib/lutaml/model/serialize.rb'
43
- - 'lib/lutaml/model/type.rb'
44
45
  - 'lib/lutaml/model/xml_adapter/nokogiri_adapter.rb'
45
46
  - 'lib/lutaml/model/xml_adapter/ox_adapter.rb'
46
47
  - 'lib/lutaml/model/xml_adapter/xml_document.rb'
@@ -52,19 +53,19 @@ Metrics/AbcSize:
52
53
  Metrics/BlockLength:
53
54
  Max: 47
54
55
 
55
- # Offense count: 27
56
+ # Offense count: 26
56
57
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
57
58
  Metrics/CyclomaticComplexity:
58
59
  Exclude:
59
60
  - 'lib/lutaml/model/attribute.rb'
60
61
  - 'lib/lutaml/model/comparable_model.rb'
61
62
  - 'lib/lutaml/model/serialize.rb'
62
- - 'lib/lutaml/model/type.rb'
63
+ - 'lib/lutaml/model/type/integer.rb'
63
64
  - 'lib/lutaml/model/xml_adapter/nokogiri_adapter.rb'
64
65
  - 'lib/lutaml/model/xml_adapter/ox_adapter.rb'
65
66
  - 'lib/lutaml/model/xml_adapter/xml_document.rb'
66
67
 
67
- # Offense count: 50
68
+ # Offense count: 53
68
69
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
69
70
  Metrics/MethodLength:
70
71
  Max: 46
@@ -74,13 +75,14 @@ Metrics/MethodLength:
74
75
  Metrics/ParameterLists:
75
76
  Max: 13
76
77
 
77
- # Offense count: 22
78
+ # Offense count: 23
78
79
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
79
80
  Metrics/PerceivedComplexity:
80
81
  Exclude:
81
82
  - 'lib/lutaml/model/attribute.rb'
82
83
  - 'lib/lutaml/model/comparable_model.rb'
83
84
  - 'lib/lutaml/model/serialize.rb'
85
+ - 'lib/lutaml/model/type/integer.rb'
84
86
  - 'lib/lutaml/model/xml_adapter/nokogiri_adapter.rb'
85
87
  - 'lib/lutaml/model/xml_adapter/ox_adapter.rb'
86
88
  - 'lib/lutaml/model/xml_adapter/xml_document.rb'
@@ -95,7 +97,7 @@ RSpec/ContextWording:
95
97
  - 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
96
98
  - 'spec/lutaml/model/xml_mapping_spec.rb'
97
99
 
98
- # Offense count: 110
100
+ # Offense count: 117
99
101
  # Configuration parameters: CountAsOne.
100
102
  RSpec/ExampleLength:
101
103
  Max: 54
@@ -106,13 +108,14 @@ RSpec/IndexedLet:
106
108
  Exclude:
107
109
  - 'spec/address_spec.rb'
108
110
 
109
- # Offense count: 19
111
+ # Offense count: 20
110
112
  RSpec/LeakyConstantDeclaration:
111
113
  Exclude:
112
114
  - 'spec/lutaml/model/schema/json_schema_spec.rb'
113
115
  - 'spec/lutaml/model/schema/relaxng_schema_spec.rb'
114
116
  - 'spec/lutaml/model/schema/xsd_schema_spec.rb'
115
117
  - 'spec/lutaml/model/schema/yaml_schema_spec.rb'
118
+ - 'spec/lutaml/model/type_spec.rb'
116
119
  - 'spec/lutaml/model/validation_spec.rb'
117
120
  - 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
118
121
 
@@ -124,7 +127,7 @@ RSpec/MultipleDescribes:
124
127
  - 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
125
128
  - 'spec/lutaml/model/xml_adapter_spec.rb'
126
129
 
127
- # Offense count: 123
130
+ # Offense count: 149
128
131
  RSpec/MultipleExpectations:
129
132
  Max: 14
130
133
 
@@ -133,24 +136,34 @@ RSpec/MultipleExpectations:
133
136
  RSpec/MultipleMemoizedHelpers:
134
137
  Max: 9
135
138
 
136
- # Offense count: 4
139
+ # Offense count: 9
137
140
  # Configuration parameters: AllowedGroups.
138
141
  RSpec/NestedGroups:
139
142
  Max: 4
140
143
 
141
- # Offense count: 7
144
+ # Offense count: 13
142
145
  RSpec/PendingWithoutReason:
143
146
  Exclude:
144
147
  - 'spec/lutaml/model/mixed_content_spec.rb'
148
+ - 'spec/lutaml/model/type/date_time_spec.rb'
149
+ - 'spec/lutaml/model/type/integer_spec.rb'
150
+ - 'spec/lutaml/model/type/time_spec.rb'
151
+ - 'spec/lutaml/model/type/time_without_date_spec.rb'
145
152
  - 'spec/lutaml/model/validation_spec.rb'
146
153
  - 'spec/lutaml/model/xml_adapter/oga_adapter_spec.rb'
147
154
  - 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
148
155
  - 'spec/lutaml/model/xml_adapter_spec.rb'
149
156
 
150
- # Offense count: 1
157
+ # Offense count: 2
151
158
  RSpec/RemoveConst:
152
159
  Exclude:
153
160
  - 'spec/lutaml/model/type/decimal_spec.rb'
161
+ - 'spec/lutaml/model/type_spec.rb'
162
+
163
+ # Offense count: 2
164
+ RSpec/RepeatedExampleGroupBody:
165
+ Exclude:
166
+ - 'spec/lutaml/model/type/time_without_date_spec.rb'
154
167
 
155
168
  # Offense count: 2
156
169
  RSpec/RepeatedExampleGroupDescription:
@@ -167,6 +180,12 @@ RSpec/SpecFilePathFormat:
167
180
  - 'spec/lutaml/model/defaults_spec.rb'
168
181
  - 'spec/lutaml/model/type/decimal_spec.rb'
169
182
 
183
+ # Offense count: 1
184
+ # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
185
+ RSpec/VerifiedDoubles:
186
+ Exclude:
187
+ - 'spec/lutaml/model/type/hash_spec.rb'
188
+
170
189
  # Offense count: 1
171
190
  Security/CompoundHash:
172
191
  Exclude:
data/README.adoc CHANGED
@@ -22,6 +22,17 @@ NOTE: Instructions on how to migrate from Shale to Lutaml::Model are provided in
22
22
  <<migrate-from-shale>>.
23
23
 
24
24
 
25
+ == Features
26
+
27
+ * Define models with attributes and types
28
+ * Serialize and deserialize models to/from JSON, XML, YAML, and TOML
29
+ * Support for multiple serialization libraries (e.g., `toml-rb`, `tomlib`)
30
+ * Configurable adapters for different serialization formats
31
+ * Support for collections and default values
32
+ * Custom serialization/deserialization methods
33
+ * XML namespaces and mappings
34
+
35
+
25
36
  == Data modeling in a nutshell
26
37
 
27
38
  Data modeling is the process of creating a data model for the data to be stored
@@ -32,16 +43,110 @@ Lutaml::Model simplifies data modeling in Ruby by allowing you to define models
32
43
  with attributes and serialize/deserialize them to/from various serialization
33
44
  formats seamlessly.
34
45
 
46
+ The Lutaml::Model data modelling approach is as follows:
47
+
48
+ .Modeling relationships of a LutaML Model
49
+ [source]
50
+ ----
51
+ Lutaml Model
52
+
53
+ Has many attributes
54
+
55
+
56
+ Attribute
57
+
58
+ Has type of
59
+
60
+ ┌──────────┴──────────┐
61
+ │ │
62
+ Model Value (Leaf)
63
+ │ │
64
+ Has many attributes Contains one basic value
65
+ │ │
66
+ ┌───────┴─────┐ ┌──────┴──────┐
67
+ │ │ │ │
68
+ Model Value (Leaf) String Integer
69
+ │ Date Boolean
70
+ │ Time Float
71
+ Has many attributes ... ...
72
+
73
+
74
+ (Recursive pattern continues...)
75
+ ----
76
+
77
+ .Example of LutaML Model instance with assigned values
78
+ ====
79
+ [source]
80
+ ----
81
+ Studio (Model)
82
+ ├── name (Value: String) = "Pottery Studio"
83
+ ├── address (Model)
84
+ │ ├── street (Value: String) = "123 Clay St"
85
+ │ ├── city (Value: String) = "Ceramics City"
86
+ │ └── postcode (Value: String) = "12345"
87
+ ├── established (Value: Date) = 2020-01-01
88
+ └── kilns (Model)
89
+ ├── count (Value: Integer) = 3
90
+ └── temperature (Value: Float) = 1200.0
91
+ ----
92
+ ====
93
+
94
+
95
+ .Modeling relationships of a LutaML Model to serialization models
96
+ [source]
97
+ ----
98
+ Core Model Serialization Model
99
+ ========== ===================
100
+ │ (mapping)
101
+ │ │
102
+ ▼ ▼
103
+ Model XML Model
104
+ │ │
105
+ ┌─────┴─────┐ ┌──────┴──────┐
106
+ │ │ │ │
107
+ Models Value Types ────┬──► Models Value Types
108
+ │ │ │ │ │
109
+ │ │ │ │ │
110
+ │ ┌──────┴──┐ │ ┌────┴────┐ ┌─┴─┐
111
+ │ │ │ │ │ │ │ │
112
+ │ String Integer │ Element Value xs:string
113
+ │ Date Float │ Attribute Type xs:date
114
+ │ Time Boolean │ xs:boolean
115
+ │ │ xs:anyURI
116
+ └──────┐ │
117
+ │ │ JSON Model
118
+ Contains │ │
119
+ more Models │ ┌──────┴──────┐
120
+ (recursive) │ │ │
121
+ └──► Models Value Types
122
+ │ │
123
+ │ │
124
+ ┌────┴────┐ ┌─┴─┐
125
+ │ │ │ │
126
+ object array number string
127
+ value boolean null
128
+ ----
129
+
130
+ .Example of LutaML Model instance transformed into a serialization model and serialized to JSON
131
+ ====
132
+ [source]
133
+ ----
134
+ Studio (Core Model) JSON Model Serialized JSON
135
+ │ │ │
136
+ ▼ ▼ ▼
137
+ name: "Studio 1" ┌─► { "name": "...", {
138
+ address: │ "address": { "name": "Studio 1",
139
+ ├── street: "..." │ "street": "...", "address": {
140
+ └── city: "..." │ "city": "..." ──► "street": "...",
141
+ kilns: │ }, "city": "..."
142
+ ├── count: 3 │ "kilnsCount": ..., },
143
+ └── temp: 1200 │ "kilnsTemp": ... "kilnsCount": 3,
144
+ └─► } "kilnsTemp": 1200
145
+ }
146
+ ----
147
+ ====
35
148
 
36
- == Features
37
149
 
38
- * Define models with attributes and types
39
- * Serialize and deserialize models to/from JSON, XML, YAML, and TOML
40
- * Support for multiple serialization libraries (e.g., `toml-rb`, `tomlib`)
41
- * Configurable adapters for different serialization formats
42
- * Support for collections and default values
43
- * Custom serialization/deserialization methods
44
- * XML namespaces and mappings
45
150
 
46
151
  == Installation
47
152
 
@@ -150,6 +255,8 @@ same class and all their attributes are equal.
150
255
  Lutaml::Model supports the following attribute types, they can be
151
256
  referred by a string, a symbol, or their class constant.
152
257
 
258
+ Every type has a corresponding Ruby class and a serialization format type.
259
+
153
260
  Syntax:
154
261
 
155
262
  [source,ruby]
@@ -159,20 +266,20 @@ attribute :name_of_attribute, {symbol | string | class}
159
266
 
160
267
  .Mapping between Lutaml::Model::Type classes, Ruby equivalents and serialization format types
161
268
  |===
162
- | Lutaml::Model::Type | Ruby Class | XML | JSON | YAML | Example Value
163
-
164
- | `:string` | `String` | `xs:string` | `string` | `string` | `"text"`
165
- | `:integer` | `Integer` | `xs:integer` | `number` | `integer` | `42`
166
- | `:float` | `Float` | `xs:decimal` | `number` | `float` | `3.14`
167
- | `:boolean` | `TrueClass`/`FalseClass` | `xs:boolean` | `boolean` | `boolean` | `true/false`
168
- | `:date` | `Date` | `xs:date` | `string` (ISO8601) | `date` | `"2024-01-01"`
169
- | `:time_without_date` | `Time` | `xs:time` | `string` (ISO8601) | `time` | `"12:34:56"`
170
- | `:date_time` | `DateTime` | `xs:dateTime` | `string` (ISO8601) | `timestamp` | `"2024-01-01T00:00:00Z"`
171
- | `:time` | `Time` | `xs:dateTime` | `string` (ISO8601) | `timestamp` | `"2024-01-01T00:00:00Z"`
172
- | `:decimal` (optional) | `BigDecimal` | `xs:decimal` | `number` | `float` | `123.45`
173
- | `:hash` | `Hash` | complex element | `object` | `map` | `{key: "value"}`
174
- | `class` | Custom Class | complex element | `object` | `map` | `CustomObject`
175
- | `collection: true` | `Array` of Type | repeated elements | `array` | `sequence` | `[obj1, obj2]`
269
+ | Lutaml::Model::Type | Ruby class | XML | JSON | YAML | Example value
270
+
271
+ | `:string` | `String` | `xs:string` | `string` | `string` | `"text"`
272
+ | `:integer` | `Integer` | `xs:integer` | `number` | `integer` | `42`
273
+ | `:float` | `Float` | `xs:decimal` | `number` | `float` | `3.14`
274
+ | `:boolean` | `TrueClass`/`FalseClass` | `xs:boolean` | `boolean` | `boolean` | `true`, `false`
275
+ | `:date` | `Date` | `xs:date` | `string` | `string` | `2024-01-01` (JSON/YAML `"2024-01-01"`)
276
+ | `:time_without_date` | `Time` | `xs:time` | `string` | `string` | `"12:34:56"`
277
+ | `:date_time` | `DateTime` | `xs:dateTime` | `string` | `string` | `"2024-01-01T12:00:00+00:00"`
278
+ | `:time` | `Time` | `xs:dateTime` | `string` | `string` | `"2024-01-01T12:00:00+00:00"`
279
+ | `:decimal` (optional) | `BigDecimal` | `xs:decimal` | `number` | `float` | `123.45`
280
+ | `:hash` | `Hash` | complex element | object | map | `{key: "value"}`
281
+ // | `class` | Custom class | complex element | object | map | `CustomObject`
282
+ // | `collection: true` | `Array` of type | repeated elements | array | sequence | `[obj1, obj2]`
176
283
  // | `any`
177
284
 
178
285
  |===
@@ -204,14 +311,17 @@ end
204
311
  ----
205
312
  ====
206
313
 
207
- ==== (optional) Decimal type
314
+ ==== Decimal type
208
315
 
209
- The `BigDecimal` class is no longer part of the standard Ruby library from Ruby
210
- 3.4 onwards, hence the `Decimal` type is only enabled when the `bigdecimal`
211
- library is loaded.
316
+ The Decimal type is an optional type that is disabled by default.
212
317
 
213
- This means that the following code needs to be run before using (and parsing)
214
- the `Decimal` type:
318
+ NOTE: The reason why the Decimal type is disalbed by default is that the
319
+ `BigDecimal` class became optional to the standard Ruby library from Ruby 3.4
320
+ onwards. The `Decimal` type is only enabled when the `bigdecimal` library is
321
+ loaded.
322
+
323
+ The following code needs to be run before using (and parsing) the Decimal
324
+ type:
215
325
 
216
326
  [source,ruby]
217
327
  ----
@@ -222,6 +332,142 @@ If the `bigdecimal` library is not loaded, usage of the `Decimal` type will
222
332
  raise a `Lutaml::Model::TypeNotSupportedError`.
223
333
 
224
334
 
335
+ ==== Custom type
336
+
337
+ A custom class can be used as an attribute type. The custom class must inherit
338
+ from `Lutaml::Model::Type::Value` or a class that inherits from it.
339
+
340
+ A class inheriting from the `Value` class carries the attribute `value` which
341
+ stores the one-and-only "true" value that is independent of serialization
342
+ formats.
343
+
344
+ The minimum requirement for a custom class is to implement the following
345
+ methods:
346
+
347
+ `self.cast(value)`:: Assignment of an external value to the `Value` class to be
348
+ set as `value`. Casts the value to the custom type.
349
+
350
+ `self.serialize(value)`:: Serializes the custom type to an object (e.g. a
351
+ string). Takes the internal `value` and converts it into an output suitable for
352
+ serialization.
353
+
354
+
355
+ .Using a custom value type to normalize a postcode with minimal methods
356
+ [example]
357
+ ====
358
+ [source,ruby]
359
+ ----
360
+ class FiveDigitPostCode < Lutaml::Model::Type::String
361
+ def self.cast(value)
362
+ value = value.to_s if value.is_a?(Integer)
363
+
364
+ unless value.is_a?(::String)
365
+ raise Lutaml::Model::InvalidValueError, "Invalid value for type 'FiveDigitPostCode'"
366
+ end
367
+
368
+ # Pad zeros to the left
369
+ value.rjust(5, '0')
370
+ end
371
+
372
+ def self.serialize(value)
373
+ value
374
+ end
375
+ end
376
+
377
+ class Studio < Lutaml::Model::Serializable
378
+ attribute :postcode, FiveDigitPostCode
379
+ end
380
+ ----
381
+ ====
382
+
383
+
384
+ ==== Serialization of custom types
385
+
386
+ The serialization of custom types can be made to differ per serialization format
387
+ by defining methods in the class definitions. This requires additional methods
388
+ than the minimum required for a custom class (i.e. `self.cast(value)` and
389
+ `self.serialize(value)`).
390
+
391
+ This is useful in the case when different serialization formats of the same
392
+ model expect differentiated value representations.
393
+
394
+ The methods that can be overridden are named:
395
+
396
+ `self.from_{format}(serialized_string)`:: Deserializes a string of the
397
+ serialization format and returns the object to be assigned to the `Value` class'
398
+ `value`.
399
+
400
+ `to_{format}`:: Serializes the object to a string of the serialization format.
401
+
402
+ The `{format}` part of the method name is the serialization format in lowercase
403
+ (e.g. `json`, `xml`, `yaml`, `toml`).
404
+
405
+ .Using custom serialization methods to handle a high-precision date-time type
406
+ [example]
407
+ ====
408
+ Suppose in XML we handle a high-precision date-time type that requires custom
409
+ serialization methods, but other formats such as JSON do not support this type.
410
+
411
+ For instance, in the normal DateTime class, the serialized string is
412
+ `2012-04-07T01:51:37+02:00`, and the high-precision format is
413
+ `2012-04-07T01:51:37.112+02:00`.
414
+
415
+ We create `HighPrecisionDateTime` class is a custom class that inherits
416
+ from `Lutaml::Model::Type::DateTime`.
417
+
418
+ [source,ruby]
419
+ ----
420
+ class HighPrecisionDateTime < Lutaml::Model::Type::DateTime
421
+ # Inherit the `self.cast(value)` and `self.serialize(value)` methods
422
+ # from Lutaml::Model::Type::DateTime
423
+
424
+ # The format looks like this `2012-04-07T01:51:37.112+02:00`
425
+ def self.from_xml(xml_string)
426
+ ::DateTime.parse(xml_string)
427
+ end
428
+
429
+ # The %L adds milliseconds to the time
430
+ def to_xml
431
+ value.strftime('%Y-%m-%dT%H:%M:%S.%L%:z')
432
+ end
433
+ end
434
+
435
+ class Ceramic < Lutaml::Model::Serializable
436
+ attribute :kiln_firing_time, HighPrecisionDateTime
437
+ xml do
438
+ root 'ceramic'
439
+ map_element 'kilnFiringTime', to: :kiln_firing_time
440
+ # ...
441
+ end
442
+ end
443
+ ----
444
+
445
+ An XML snippet with the high-precision date-time type:
446
+
447
+ [source,xml]
448
+ ----
449
+ <ceramic>
450
+ <kilnFiringTime>2012-04-07T01:51:37.112+02:00</kilnFiringTime>
451
+ <!-- ... -->
452
+ </ceramic>
453
+ ----
454
+
455
+ When loading the XML snippet, the `HighPrecisionDateTime` class will be used to
456
+ parse the high-precision date-time string.
457
+
458
+ However, when serializing to JSON, the value will have the high-precision
459
+ part lost due to the inability of JSON to handle high-precision date-time.
460
+
461
+ [source,ruby]
462
+ ----
463
+ > c = Ceramic.from_xml(xml)
464
+ > #<Ceramic:0x0000000104ac7240 @kiln_firing_time=#<HighPrecisionDateTime:0x0000000104ac7240 @value=2012-04-07 01:51:37.112000000 +0200>>
465
+ > c.to_json
466
+ > # {"kilnFiringTime":"2012-04-07T01:51:37+02:00"}
467
+ ----
468
+ ====
469
+
470
+
225
471
  === Attribute as a collection
226
472
 
227
473
  Define attributes as collections (arrays or hashes) to store multiple values
@@ -34,15 +34,23 @@ module Lutaml
34
34
 
35
35
  def cast_type!(type)
36
36
  case type
37
+ when Symbol
38
+ begin
39
+ Type.lookup(type)
40
+ rescue UnknownTypeError
41
+ raise ArgumentError, "Unknown Lutaml::Model::Type: #{type}"
42
+ end
43
+ when String
44
+ begin
45
+ Type.const_get(type)
46
+ rescue NameError
47
+ raise ArgumentError, "Unknown Lutaml::Model::Type: #{type}"
48
+ end
37
49
  when Class
38
50
  type
39
- when String
40
- Type.const_get(type)
41
- when Symbol
42
- Type.const_get(type.to_s.split("_").collect(&:capitalize).join)
51
+ else
52
+ raise ArgumentError, "Unknown Lutaml::Model::Type: #{type}"
43
53
  end
44
- rescue NameError
45
- raise ArgumentError, "Unknown Lutaml::Model::Type: #{type}"
46
54
  end
47
55
 
48
56
  def cast_value(value)
@@ -194,7 +202,9 @@ module Lutaml
194
202
  elsif type <= Serialize
195
203
  type.hash_representation(value, format, options)
196
204
  else
197
- type.serialize(value)
205
+ # Convert to Value instance if not already
206
+ value = type.new(value) unless value.is_a?(Type::Value)
207
+ value.send(:"to_#{format}")
198
208
  end
199
209
  end
200
210
 
@@ -208,7 +218,7 @@ module Lutaml
208
218
  elsif type <= Serialize && value.is_a?(Hash)
209
219
  type.apply_mappings(value, format, options)
210
220
  else
211
- Lutaml::Model::Type.cast(value, type)
221
+ type.cast(value)
212
222
  end
213
223
  end
214
224
 
@@ -0,0 +1,9 @@
1
+ module Lutaml
2
+ module Model
3
+ class TypeError < Error
4
+ def initialize(message)
5
+ super("Type Error: #{message}")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Lutaml
2
+ module Model
3
+ class UnknownTypeError < Error
4
+ def initialize(type_name)
5
+ super("Unknown type '#{type_name}'")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,4 +1,3 @@
1
- # lib/lutaml/model/error/validation_error.rb
2
1
  module Lutaml
3
2
  module Model
4
3
  class ValidationError < Error
@@ -11,3 +11,5 @@ require_relative "error/unknown_adapter_type_error"
11
11
  require_relative "error/collection_count_out_of_range_error"
12
12
  require_relative "error/validation_error"
13
13
  require_relative "error/type_not_enabled_error"
14
+ require_relative "error/type_error"
15
+ require_relative "error/unknown_type_error"
@@ -310,7 +310,10 @@ module Lutaml
310
310
  def apply_xml_mapping(doc, instance, options = {})
311
311
  return instance unless doc
312
312
 
313
- options[:default_namespace] = mappings_for(:xml)&.namespace_uri if options[:default_namespace].nil?
313
+ if options[:default_namespace].nil?
314
+ options[:default_namespace] =
315
+ mappings_for(:xml)&.namespace_uri
316
+ end
314
317
  mappings = mappings_for(:xml).mappings
315
318
 
316
319
  if doc.is_a?(Array)
@@ -398,6 +401,8 @@ module Lutaml
398
401
  value.map do |v|
399
402
  text_hash?(attr, v) ? v["text"] : v
400
403
  end
404
+ elsif attr&.raw? && value
405
+ value.node.children.map(&:to_xml).join
401
406
  elsif text_hash?(attr, value)
402
407
  value["text"]
403
408
  else
@@ -0,0 +1,38 @@
1
+ module Lutaml
2
+ module Model
3
+ module Type
4
+ class Boolean < Value
5
+ def self.cast(value)
6
+ return nil if value.nil?
7
+ return true if value == true || value.to_s.match?(/^(true|t|yes|y|1)$/i)
8
+ return false if value == false || value.to_s.match?(/^(false|f|no|n|0)$/i)
9
+
10
+ value
11
+ end
12
+
13
+ def self.serialize(value)
14
+ return nil if value.nil?
15
+
16
+ cast(value) # Return actual boolean instead of string
17
+ end
18
+
19
+ # Format-specific serialization methods
20
+ def to_xml
21
+ value.to_s
22
+ end
23
+
24
+ def to_json(*_args)
25
+ value
26
+ end
27
+
28
+ def to_yaml
29
+ value
30
+ end
31
+
32
+ def to_toml
33
+ value.to_s
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end