lutaml-model 0.3.23 → 0.3.25

Sign up to get free protection for your applications and to get access to all the features.
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 +7 -2
  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: e7e401ce23a01b4dc80f709b71326b598cb9b9024b81d2a94f501bebf8112a14
4
- data.tar.gz: 30534172ee835a9170e4335029702ec87ec4225c52e9e09566cb694fbd9c258d
3
+ metadata.gz: 923c122ff4459f02c1949dc88ee8df94b93db6ce23c00c9da2159f6b376da3a0
4
+ data.tar.gz: aa537363d67a5383cdbd280cfa9ddee0a3c5de0cb151b79b2e195a030bc0061d
5
5
  SHA512:
6
- metadata.gz: ce9d2be7aac031cba94181053fa59f19cb61ed6fafbdda177bec369a03fd92d53dcfc9caf43a34ebb439c7d69d2bf3f0db1a56a49e5cc34021a8fe4a0fc7fe25
7
- data.tar.gz: 187168f579188ad1458013375fd5bacb6718dbb29d77994229c1c11fb14bcec5df8bae8bcfbe42dc3e57a5918c663741df7168338a2df36869562613aa06d149
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-03 22:29:56 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: 124
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: 37
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: 51
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: 125
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"
@@ -35,7 +35,7 @@ module Lutaml
35
35
  subclass.instance_variable_set(:@attributes,
36
36
  Utils.deep_dup(@attributes))
37
37
  subclass.instance_variable_set(:@mappings, Utils.deep_dup(@mappings))
38
- subclass.instance_variable_set(:@model, @model || subclass)
38
+ subclass.instance_variable_set(:@model, subclass)
39
39
  end
40
40
 
41
41
  def model(klass = nil)
@@ -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