lutaml-model 0.8.126 → 0.8.128

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +60 -0
  3. data/docs/_guides/keyvalue-serialization.adoc +75 -0
  4. data/docs/_pages/value_types.adoc +102 -0
  5. data/lib/compat/opal/lutaml_model_boot.rb +15 -0
  6. data/lib/lutaml/key_value/transform.rb +238 -30
  7. data/lib/lutaml/model/error/unknown_mapping_error.rb +18 -0
  8. data/lib/lutaml/model/schema/generator/property.rb +60 -9
  9. data/lib/lutaml/model/serialize/format_conversion.rb +115 -2
  10. data/lib/lutaml/model/serialize/transformation_builder.rb +4 -3
  11. data/lib/lutaml/model/transform.rb +29 -4
  12. data/lib/lutaml/model/transformation_registry.rb +7 -5
  13. data/lib/lutaml/model/type/date_time_with_timezone.rb +28 -0
  14. data/lib/lutaml/model/type/date_with_timezone.rb +28 -0
  15. data/lib/lutaml/model/type/day_time_duration.rb +35 -0
  16. data/lib/lutaml/model/type/email_address.rb +28 -0
  17. data/lib/lutaml/model/type/hostname.rb +30 -0
  18. data/lib/lutaml/model/type/ip_v4_address.rb +27 -0
  19. data/lib/lutaml/model/type/ip_v6_address.rb +47 -0
  20. data/lib/lutaml/model/type/metaschema_pattern.rb +34 -0
  21. data/lib/lutaml/model/type/non_negative_integer.rb +25 -0
  22. data/lib/lutaml/model/type/positive_integer.rb +25 -0
  23. data/lib/lutaml/model/type/token.rb +25 -0
  24. data/lib/lutaml/model/type/uri_reference.rb +29 -0
  25. data/lib/lutaml/model/type/uuid.rb +25 -0
  26. data/lib/lutaml/model/type/value.rb +9 -0
  27. data/lib/lutaml/model/type/year_month_duration.rb +29 -0
  28. data/lib/lutaml/model/type.rb +27 -0
  29. data/lib/lutaml/model/version.rb +1 -1
  30. data/lib/lutaml/model.rb +2 -0
  31. data/lib/lutaml/xml/adapter/xml_serializer.rb +8 -6
  32. data/lib/lutaml/xml/model_transform.rb +8 -4
  33. data/lib/lutaml/xml/schema/xsd_schema.rb +30 -9
  34. data/lib/lutaml/xml/transformation.rb +3 -2
  35. data/spec/lutaml/key_value/child_map_plan_spec.rb +169 -0
  36. data/spec/lutaml/model/metaschema_types_spec.rb +367 -0
  37. data/spec/lutaml/model/serialization_profiles_spec.rb +276 -0
  38. metadata +20 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b25c117248ae09d0b9a264e2d28fd7a34e8701e97c745c0169c92e53309a70b
4
- data.tar.gz: ca1949cf1b6b3ad95b3640c14ed80ef34f6d741e7f6d66b50b9ad6d7a42664f1
3
+ metadata.gz: d822754e3a7baeb6edb87dcfa7fb61c90c5da8b44920b2ad612905cb8ee9a278
4
+ data.tar.gz: ef8bcf9d531357fcd8d3fd07fc5053015c1b0e6202530866adf01f7e932123de
5
5
  SHA512:
6
- metadata.gz: dc5fe5fc88f3a1744f61e447dbb863f22f2d3314086b681d785d90e62f1ca2ef1e42bb156edae5bbd6f920ea3628f877623a16a0f8bfa49022ca93200eda887d
7
- data.tar.gz: 46c3f3966f2d7bf6a14292cb311544d40b5bdb561dd9a0a17c21d06ac257fd71ff9d1e427bb14697e781888c2aaade62a2e28c6bdbea2e89e316645b4c366428
6
+ metadata.gz: 9c4c5d63a666d0b8b99279de9dcf7fbee3031b694abba3729636249f701a4f67388507c13c778318f61d22ac1ce9a21940bf35960299e2a569fb024e302e3560
7
+ data.tar.gz: 7eb201762de275df248d27f7fc7c5e1019cd65e63822059b7075a69aae661d8a18f8123522797155665d8688d8147a6e06d46fdffde8fbc35e9f4347bd8424b9
data/README.adoc CHANGED
@@ -830,6 +830,19 @@ Every type has a corresponding Ruby class and a serialization format type.
830
830
  | `:qname` | `Lutaml::Model::Type::QName` | `xs:QName` | `string` | `string` | `"prefix:localName"`
831
831
  | `:base64_binary` | `String` | `xs:base64Binary` | `string` | `string` | `"SGVsbG8gV29ybGQ="`
832
832
  | `:hex_binary` | `String` | `xs:hexBinary` | `string` | `string` | `"48656c6c6f"`
833
+ | `:email_address` | `String` | `xs:string` | `string` | `string` | `"user@example.com"`
834
+ | `:hostname` | `String` | `xs:string` | `string` | `string` | `"example.com"`
835
+ | `:ip_v4_address` | `String` | `xs:string` | `string` | `string` | `"192.168.0.1"`
836
+ | `:ip_v6_address` | `String` | `xs:string` | `string` | `string` | `"2001:db8::2:1"`
837
+ | `:token` | `String` | `xs:string` | `string` | `string` | `"label_3.v2"`
838
+ | `:uri_reference` | `String` | `xs:anyURI` | `string` | `string` | `"path/to/resource"`
839
+ | `:uuid` | `String` | `xs:string` | `string` | `string` | `"a3bb189e-8bf9-4888-9912-ace4e6543002"`
840
+ | `:non_negative_integer` | `Integer` | `xs:nonNegativeInteger` | `number` | `integer` | `7`
841
+ | `:positive_integer` | `Integer` | `xs:positiveInteger` | `number` | `integer` | `7`
842
+ | `:date_with_timezone` | `Date`/`DateTime` | `xs:date` | `string` | `string` | `"2019-12-02-08:00"`
843
+ | `:date_time_with_timezone` | `DateTime` | `xs:dateTime` | `string` | `string` | `"2019-12-02T16:39:57-08:00"`
844
+ | `:day_time_duration` | `Lutaml::Model::Type::Duration` | `xs:dayTimeDuration` | `string` | `string` | `"P1DT12H45M"`
845
+ | `:year_month_duration` | `Lutaml::Model::Type::Duration` | `xs:yearMonthDuration` | `string` | `string` | `"P1Y6M"`
833
846
  | `:hash` | `Hash` | complex element | object | map | `{key: "value"}`
834
847
  | (nil value) | `nil` | `xs:anyType` | `null` | `null` | `null`
835
848
  | `{ ref: [Model, :attr] }` | `Reference` | `xs:string` | `string` | `string` | `"model-id"`
@@ -840,6 +853,53 @@ Every type has a corresponding Ruby class and a serialization format type.
840
853
  |===
841
854
 
842
855
 
856
+ === NIST Metaschema types
857
+
858
+ The NIST Metaschema simple data types are supported as first-class value
859
+ types (https://pages.nist.gov/metaschema/specification/datatypes/). Each
860
+ type carries the normative metaschema-datatypes pattern, answers `valid?`
861
+ for a value, and declares it as a restriction facet: attributes typed
862
+ with these types are validated by `model.validate`, and schema
863
+ generation emits the pattern/bounds as restriction facets in every
864
+ supported schema language — `xs:pattern`/`xs:minInclusive` in generated
865
+ XSD, `pattern`/`minimum` plus the NIST `format` keywords (`ipv4`,
866
+ `ipv6`, `email`, `uri-reference`, `duration`, `date`, `date-time`) in
867
+ generated JSON and YAML schemas. Casting follows the lenient
868
+ base-type behavior; TOML has no schema language, so its surface for
869
+ these types is the cast/serialize lane.
870
+
871
+ .Source types
872
+ [cols="3,4,2"]
873
+ |===
874
+ | Type code | Metaschema datatype | Restriction
875
+
876
+ | `:email_address` | `email-address` | contains an at-sign
877
+ | `:hostname` | `hostname` | non-empty, trimmed
878
+ | `:ip_v4_address` | `ip-v4-address` | dotted-quad IPv4
879
+ | `:ip_v6_address` | `ip-v6-address` | RFC3513 IPv6 (incl. zones, embedded IPv4)
880
+ | `:token` | `token` | NMTOKEN-like letter/number/punctuation run
881
+ | `:uri_reference` | `uri-reference` | relative or absolute URI reference
882
+ | `:uuid` | `uuid` | RFC4122 UUID, version 4 or 5
883
+ | `:non_negative_integer` | `non-negative-integer` | integer >= 0
884
+ | `:positive_integer` | `positive-integer` | integer >= 1
885
+ | `:date_with_timezone` | `date-with-timezone` | date with `Z` or offset
886
+ | `:date_time_with_timezone` | `date-time-with-timezone` | date-time with `Z` or offset
887
+ | `:day_time_duration` | `day-time-duration` | duration without years/months
888
+ | `:year_month_duration` | `year-month-duration` | duration without time components
889
+ |===
890
+
891
+ .Usage
892
+ [source,ruby]
893
+ ----
894
+ class Asset < Lutaml::Model::Serializable
895
+ attribute :id, :uuid
896
+ attribute :owner, :email_address
897
+ attribute :host, :hostname
898
+ attribute :address, :ip_v4_address
899
+ attribute :renewal, :year_month_duration
900
+ end
901
+ ----
902
+
843
903
  === Decimal type
844
904
 
845
905
  The Decimal type uses Ruby's `BigDecimal` for arbitrary-precision decimal
@@ -1188,3 +1188,78 @@ In this example:
1188
1188
  `name` attributes, respectively.
1189
1189
  ====
1190
1190
 
1191
+
1192
+ == Serialization mapping profiles
1193
+
1194
+ A model can declare multiple serialization mappings for the same format —
1195
+ *serialization mapping profiles* (also called projections or lenses). Each
1196
+ profile is a complete mapping: what it maps is the projection, and
1197
+ anything it does not map stays outside it. The base mapping (declared
1198
+ without a name) remains the default.
1199
+
1200
+ === Declaring a profile
1201
+
1202
+ [source,ruby]
1203
+ ----
1204
+ class Person < Lutaml::Model::Serializable
1205
+ attribute :name, :string
1206
+ attribute :email, :string
1207
+ attribute :internal_id, :string
1208
+
1209
+ json do
1210
+ map "name", to: :name
1211
+ map "email", to: :email
1212
+ map "internal_id", to: :internal_id
1213
+ end
1214
+
1215
+ json :compact do
1216
+ map "name", to: :name
1217
+ end
1218
+ end
1219
+ ----
1220
+
1221
+ === Selecting a profile
1222
+
1223
+ Profiles are selected per call with the `mapping:` option, for both
1224
+ serialization and parsing. An unknown profile name raises
1225
+ `Lutaml::Model::UnknownMappingError`.
1226
+
1227
+ [source,ruby]
1228
+ ----
1229
+ person.to_json # => {"name"=>"Ed", ...}
1230
+ person.to_json(mapping: :compact) # => {"name"=>"Ed"}
1231
+ Person.from_json(json, mapping: :compact)
1232
+ ----
1233
+
1234
+ `only:` and `except:` compose with the selected profile. Child models
1235
+ always serialize through their own mappings; a parent's profile does not
1236
+ propagate to them.
1237
+
1238
+ === Runtime-constructed mappings
1239
+
1240
+ A mapping object can be built at runtime and wired to the model, or used
1241
+ directly for a single call. This supports projections that are not known
1242
+ at class-definition time.
1243
+
1244
+ [source,ruby]
1245
+ ----
1246
+ mapping = Lutaml::Model::Config.mappings_class_for(:yaml).new
1247
+ mapping.map "full_name", to: :name
1248
+
1249
+ Person.mapping :public, mapping # register as a named profile
1250
+ person.to_yaml(mapping: :public) # by name
1251
+
1252
+ person.to_yaml(mapping: mapping) # ephemeral, unwired single use
1253
+ ----
1254
+
1255
+ `Person.mapping :name, nil, format: :yaml` removes a registered profile.
1256
+ `Person.serialization_mappings(:yaml)` returns the registered profiles of
1257
+ a format. Re-declaring or re-registering a name replaces the previous
1258
+ mapping.
1259
+
1260
+ XML profiles follow the same shape: `xml :registry do ... end` declares a
1261
+ profile with its own root element, and `to_xml(mapping: :registry)` /
1262
+ `from_xml(data, mapping: :registry)` select it. XSD generation honors
1263
+ the selection too: `Lutaml::Model::Schema.to_xsd(Record, mapping:
1264
+ :registry)` generates the schema of that projection, leaving unmapped
1265
+ attributes outside it.
@@ -35,6 +35,19 @@ Every type has a corresponding Ruby class and a serialization format type.
35
35
  | `:qname` | `Lutaml::Model::Type::QName` | `xs:QName` | `string` | `string` | `"prefix:localName"`
36
36
  | `:base64_binary` | `String` | `xs:base64Binary` | `string` | `string` | `"SGVsbG8gV29ybGQ="`
37
37
  | `:hex_binary` | `String` | `xs:hexBinary` | `string` | `string` | `"48656c6c6f"`
38
+ | `:email_address` | `String` | `xs:string` | `string` | `string` | `"user@example.com"`
39
+ | `:hostname` | `String` | `xs:string` | `string` | `string` | `"example.com"`
40
+ | `:ip_v4_address` | `String` | `xs:string` | `string` | `string` | `"192.168.0.1"`
41
+ | `:ip_v6_address` | `String` | `xs:string` | `string` | `string` | `"2001:db8::2:1"`
42
+ | `:token` | `String` | `xs:string` | `string` | `string` | `"label_3.v2"`
43
+ | `:uri_reference` | `String` | `xs:anyURI` | `string` | `string` | `"path/to/resource"`
44
+ | `:uuid` | `String` | `xs:string` | `string` | `string` | `"a3bb189e-8bf9-4888-9912-ace4e6543002"`
45
+ | `:non_negative_integer` | `Integer` | `xs:nonNegativeInteger` | `number` | `integer` | `7`
46
+ | `:positive_integer` | `Integer` | `xs:positiveInteger` | `number` | `integer` | `7`
47
+ | `:date_with_timezone` | `Date`/`DateTime` | `xs:date` | `string` | `string` | `"2019-12-02-08:00"`
48
+ | `:date_time_with_timezone` | `DateTime` | `xs:dateTime` | `string` | `string` | `"2019-12-02T16:39:57-08:00"`
49
+ | `:day_time_duration` | `Lutaml::Model::Type::Duration` | `xs:dayTimeDuration` | `string` | `string` | `"P1DT12H45M"`
50
+ | `:year_month_duration` | `Lutaml::Model::Type::Duration` | `xs:yearMonthDuration` | `string` | `string` | `"P1Y6M"`
38
51
  | `:hash` | `Hash` | complex element | object | map | `{key: "value"}`
39
52
  | (nil value) | `nil` | `xs:anyType` | `null` | `null` | `null`
40
53
  | `{ ref: [Model, :attr] }` | `Reference` | `xs:string` | `string` | `string` | `"model-id"`
@@ -120,6 +133,56 @@ puts event.start_date.class
120
133
 
121
134
 
122
135
  [[xml-namespace-types]]
136
+ === NIST Metaschema types
137
+
138
+ The NIST Metaschema simple data types are supported as first-class value
139
+ types (https://pages.nist.gov/metaschema/specification/datatypes/). Each
140
+ type carries the normative metaschema-datatypes pattern, answers `valid?`
141
+ for a value, and declares it as a restriction facet: attributes typed
142
+ with these types are validated by `model.validate`, and schema
143
+ generation emits the pattern/bounds as restriction facets in every
144
+ supported schema language — `xs:pattern`/`xs:minInclusive` in generated
145
+ XSD, `pattern`/`minimum` plus the NIST `format` keywords (`ipv4`,
146
+ `ipv6`, `email`, `uri-reference`, `duration`, `date`, `date-time`) in
147
+ generated JSON and YAML schemas. Casting follows the lenient
148
+ base-type behavior; TOML has no schema language, so its surface for
149
+ these types is the cast/serialize lane.
150
+
151
+ .Metaschema type codes and their restrictions
152
+ [cols="3,4,2"]
153
+ |===
154
+ | Type code | Metaschema datatype | Restriction
155
+
156
+ | `:email_address` | `email-address` | contains an at-sign
157
+ | `:hostname` | `hostname` | non-empty, trimmed
158
+ | `:ip_v4_address` | `ip-v4-address` | dotted-quad IPv4
159
+ | `:ip_v6_address` | `ip-v6-address` | RFC3513 IPv6 (incl. zones, embedded IPv4)
160
+ | `:token` | `token` | NMTOKEN-like letter/number/punctuation run
161
+ | `:uri_reference` | `uri-reference` | relative or absolute URI reference
162
+ | `:uuid` | `uuid` | RFC4122 UUID, version 4 or 5
163
+ | `:non_negative_integer` | `non-negative-integer` | integer >= 0
164
+ | `:positive_integer` | `positive-integer` | integer >= 1
165
+ | `:date_with_timezone` | `date-with-timezone` | date with `Z` or offset
166
+ | `:date_time_with_timezone` | `date-time-with-timezone` | date-time with `Z` or offset
167
+ | `:day_time_duration` | `day-time-duration` | duration without years/months
168
+ | `:year_month_duration` | `year-month-duration` | duration without time components
169
+ |===
170
+
171
+ .Using metaschema type attributes
172
+ [example]
173
+ ====
174
+ [source,ruby]
175
+ ----
176
+ class Asset < Lutaml::Model::Serializable
177
+ attribute :id, :uuid
178
+ attribute :owner, :email_address
179
+ attribute :host, :hostname
180
+ attribute :address, :ip_v4_address
181
+ attribute :renewal, :year_month_duration
182
+ end
183
+ ----
184
+ ====
185
+
123
186
  === Additional XSD types
124
187
 
125
188
  Lutaml::Model supports additional XSD types for specialized data handling:
@@ -1325,6 +1388,45 @@ All built-in types have default XSD type mappings:
1325
1388
  | [`Type::HexBinary`](lib/lutaml/model/type/hex_binary.rb)
1326
1389
  | `xs:hexBinary`
1327
1390
 
1391
+ | [`Type::EmailAddress`](lib/lutaml/model/type/email_address.rb)
1392
+ | `xs:string`
1393
+
1394
+ | [`Type::Hostname`](lib/lutaml/model/type/hostname.rb)
1395
+ | `xs:string`
1396
+
1397
+ | [`Type::IpV4Address`](lib/lutaml/model/type/ip_v4_address.rb)
1398
+ | `xs:string`
1399
+
1400
+ | [`Type::IpV6Address`](lib/lutaml/model/type/ip_v6_address.rb)
1401
+ | `xs:string`
1402
+
1403
+ | [`Type::Token`](lib/lutaml/model/type/token.rb)
1404
+ | `xs:string`
1405
+
1406
+ | [`Type::UriReference`](lib/lutaml/model/type/uri_reference.rb)
1407
+ | `xs:anyURI`
1408
+
1409
+ | [`Type::Uuid`](lib/lutaml/model/type/uuid.rb)
1410
+ | `xs:string`
1411
+
1412
+ | [`Type::NonNegativeInteger`](lib/lutaml/model/type/non_negative_integer.rb)
1413
+ | `xs:nonNegativeInteger`
1414
+
1415
+ | [`Type::PositiveInteger`](lib/lutaml/model/type/positive_integer.rb)
1416
+ | `xs:positiveInteger`
1417
+
1418
+ | [`Type::DateWithTimezone`](lib/lutaml/model/type/date_with_timezone.rb)
1419
+ | `xs:date`
1420
+
1421
+ | [`Type::DateTimeWithTimezone`](lib/lutaml/model/type/date_time_with_timezone.rb)
1422
+ | `xs:dateTime`
1423
+
1424
+ | [`Type::DayTimeDuration`](lib/lutaml/model/type/day_time_duration.rb)
1425
+ | `xs:dayTimeDuration`
1426
+
1427
+ | [`Type::YearMonthDuration`](lib/lutaml/model/type/year_month_duration.rb)
1428
+ | `xs:yearMonthDuration`
1429
+
1328
1430
  | [`Type::Hash`](lib/lutaml/model/type/hash.rb)
1329
1431
  | `xs:anyType`
1330
1432
 
@@ -281,6 +281,7 @@ require "lutaml/model/error/undefined_attribute_error"
281
281
  require "lutaml/model/error/union_schema_unsupported_error"
282
282
  require "lutaml/model/error/unknown_adapter_type_error"
283
283
  require "lutaml/model/error/unknown_discriminator_error"
284
+ require "lutaml/model/error/unknown_mapping_error"
284
285
  require "lutaml/model/error/unknown_sequence_mapping_error"
285
286
  require "lutaml/model/error/unknown_type_error"
286
287
  require "lutaml/model/error/unresolvable_type_error"
@@ -333,21 +334,35 @@ require "lutaml/model/type/base64_binary"
333
334
  require "lutaml/model/type/boolean"
334
335
  require "lutaml/model/type/date"
335
336
  require "lutaml/model/type/date_time"
337
+ require "lutaml/model/type/date_time_with_timezone"
338
+ require "lutaml/model/type/date_with_timezone"
339
+ require "lutaml/model/type/day_time_duration"
336
340
  require "lutaml/model/type/decimal"
337
341
  require "lutaml/model/type/duration"
342
+ require "lutaml/model/type/email_address"
338
343
  require "lutaml/model/type/float"
339
344
  require "lutaml/model/type/hash"
340
345
  require "lutaml/model/type/hex_binary"
346
+ require "lutaml/model/type/hostname"
341
347
  require "lutaml/model/type/integer"
348
+ require "lutaml/model/type/ip_v4_address"
349
+ require "lutaml/model/type/ip_v6_address"
350
+ require "lutaml/model/type/metaschema_pattern"
351
+ require "lutaml/model/type/non_negative_integer"
352
+ require "lutaml/model/type/positive_integer"
342
353
  require "lutaml/model/type/qname"
343
354
  require "lutaml/model/type/reference"
344
355
  require "lutaml/model/type/string"
345
356
  require "lutaml/model/type/symbol"
346
357
  require "lutaml/model/type/time"
347
358
  require "lutaml/model/type/time_without_date"
359
+ require "lutaml/model/type/token"
348
360
  require "lutaml/model/type/uninitialized_class_guard"
349
361
  require "lutaml/model/type/uri"
362
+ require "lutaml/model/type/uri_reference"
363
+ require "lutaml/model/type/uuid"
350
364
  require "lutaml/model/type/value"
365
+ require "lutaml/model/type/year_month_duration"
351
366
  require "lutaml/rdf/namespaces/dcterms_namespace"
352
367
  require "lutaml/rdf/namespaces/owl_namespace"
353
368
  require "lutaml/rdf/namespaces/rdf_namespace"