lutaml-model 0.3.0 → 0.3.2

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -5
  3. data/.rubocop_todo.yml +20 -101
  4. data/Gemfile +3 -18
  5. data/README.adoc +1100 -140
  6. data/lib/lutaml/model/attribute.rb +15 -2
  7. data/lib/lutaml/model/config.rb +0 -1
  8. data/lib/lutaml/model/error/invalid_value_error.rb +18 -0
  9. data/lib/lutaml/model/error.rb +8 -0
  10. data/lib/lutaml/model/json_adapter/json_document.rb +20 -0
  11. data/lib/lutaml/model/json_adapter/json_object.rb +28 -0
  12. data/lib/lutaml/model/json_adapter/{multi_json.rb → multi_json_adapter.rb} +2 -3
  13. data/lib/lutaml/model/json_adapter/{standard.rb → standard_json_adapter.rb} +2 -3
  14. data/lib/lutaml/model/json_adapter.rb +1 -31
  15. data/lib/lutaml/model/key_value_mapping.rb +9 -2
  16. data/lib/lutaml/model/key_value_mapping_rule.rb +0 -1
  17. data/lib/lutaml/model/mapping_hash.rb +0 -2
  18. data/lib/lutaml/model/mapping_rule.rb +5 -3
  19. data/lib/lutaml/model/schema/json_schema.rb +0 -1
  20. data/lib/lutaml/model/schema/relaxng_schema.rb +0 -1
  21. data/lib/lutaml/model/schema/xsd_schema.rb +0 -1
  22. data/lib/lutaml/model/schema/yaml_schema.rb +0 -1
  23. data/lib/lutaml/model/schema.rb +0 -1
  24. data/lib/lutaml/model/serializable.rb +0 -1
  25. data/lib/lutaml/model/serialize.rb +241 -153
  26. data/lib/lutaml/model/toml_adapter/toml_document.rb +20 -0
  27. data/lib/lutaml/model/toml_adapter/toml_object.rb +28 -0
  28. data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +4 -5
  29. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +2 -3
  30. data/lib/lutaml/model/toml_adapter.rb +0 -31
  31. data/lib/lutaml/model/type/date_time.rb +20 -0
  32. data/lib/lutaml/model/type/json.rb +34 -0
  33. data/lib/lutaml/model/type/time_without_date.rb +4 -3
  34. data/lib/lutaml/model/type.rb +61 -124
  35. data/lib/lutaml/model/version.rb +1 -1
  36. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +20 -13
  37. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +4 -5
  38. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +24 -17
  39. data/lib/lutaml/model/xml_adapter/xml_attribute.rb +27 -0
  40. data/lib/lutaml/model/xml_adapter/xml_document.rb +184 -0
  41. data/lib/lutaml/model/xml_adapter/xml_element.rb +94 -0
  42. data/lib/lutaml/model/xml_adapter/xml_namespace.rb +49 -0
  43. data/lib/lutaml/model/xml_adapter.rb +0 -266
  44. data/lib/lutaml/model/xml_mapping.rb +1 -1
  45. data/lib/lutaml/model/xml_mapping_rule.rb +3 -4
  46. data/lib/lutaml/model/yaml_adapter/standard_yaml_adapter.rb +34 -0
  47. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +20 -0
  48. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +28 -0
  49. data/lib/lutaml/model/yaml_adapter.rb +1 -19
  50. data/lib/lutaml/model.rb +7 -5
  51. metadata +19 -5
  52. data/lib/lutaml/model/xml_namespace.rb +0 -47
@@ -1,170 +1,97 @@
1
- # lib/lutaml/model/type.rb
2
1
  require "date"
3
2
  require "bigdecimal"
4
3
  require "securerandom"
5
4
  require "uri"
6
5
  require "ipaddr"
7
- require "json"
8
6
 
9
7
  module Lutaml
10
8
  module Model
11
9
  module Type
12
- %w(String
13
- Integer
14
- Float
15
- Date
16
- Time
17
- Boolean
18
- Decimal
19
- Hash
20
- UUID
21
- Symbol
22
- BigInteger
23
- Binary
24
- URL
25
- Email
26
- IPAddress
27
- JSON
28
- Enum).each do |t|
10
+ # This module provides a set of methods to cast and serialize values
11
+
12
+ # TODO: Make Boolean a separate class
13
+
14
+ %w(
15
+ String
16
+ Integer
17
+ Float
18
+ Date
19
+ Time
20
+ Boolean
21
+ Decimal
22
+ Hash
23
+ Uuid
24
+ Symbol
25
+ Binary
26
+ Url
27
+ IpAddress
28
+ Json
29
+ ).each do |t|
29
30
  class_eval <<~HEREDOC, __FILE__, __LINE__ + 1
30
- class #{t} # class Integer
31
+ class #{t} # class Integer
31
32
  def self.cast(value) # def self.cast(value)
32
33
  return if value.nil? # return if value.nil?
33
-
34
34
  Type.cast(value, #{t}) # Type.cast(value, Integer)
35
35
  end # end
36
36
 
37
37
  def self.serialize(value) # def self.serialize(value)
38
38
  return if value.nil? # return if value.nil?
39
-
40
39
  Type.serialize(value, #{t}) # Type.serialize(value, Integer)
41
40
  end # end
42
41
  end # end
43
42
  HEREDOC
44
43
  end
45
44
 
46
- class TimeWithoutDate
47
- def self.cast(value)
48
- return if value.nil?
49
-
50
- ::Time.parse(value.to_s)
51
- # .strftime("%H:%M:%S")
52
- end
53
-
54
- def self.serialize(value)
55
- value.strftime("%H:%M:%S")
56
- end
57
- end
58
-
59
- class DateTime
60
- def self.cast(value)
61
- return if value.nil?
62
-
63
- ::DateTime.parse(value.to_s).new_offset(0)
64
- end
65
-
66
- def self.serialize(value)
67
- value.iso8601
68
- end
69
- end
70
-
45
+ # TODO: Remove this. The XSD code depends on this but actually should
46
+ # be converted into a collection, not a specific Array type.
71
47
  class Array
72
48
  def initialize(array)
73
49
  Array(array)
74
50
  end
75
51
  end
76
52
 
77
- class TextWithTags
78
- attr_reader :content
79
-
80
- def initialize(ordered_text_with_tags)
81
- @content = ordered_text_with_tags
82
- end
83
-
84
- def self.cast(value)
85
- return value if value.is_a?(self)
86
-
87
- new(value)
88
- end
89
-
90
- def self.serialize(value)
91
- value.content.join
92
- end
93
- end
94
-
95
- class JSON
96
- attr_reader :value
97
-
98
- def initialize(value)
99
- @value = value
100
- end
101
-
102
- def to_json(*_args)
103
- @value.to_json
104
- end
105
-
106
- def ==(other)
107
- @value == if other.is_a?(::Hash)
108
- other
109
- else
110
- other.value
111
- end
112
- end
113
-
114
- def self.cast(value)
115
- return value if value.is_a?(self) || value.nil?
116
-
117
- new(::JSON.parse(value))
118
- end
119
-
120
- def self.serialize(value)
121
- value.to_json
122
- end
123
- end
124
-
125
53
  UUID_REGEX = /\A[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\z/
126
54
 
127
55
  def self.cast(value, type)
128
56
  return if value.nil?
129
57
 
130
- if [String, Email].include?(type)
58
+ case type.to_s.split("::").last
59
+ when "String"
131
60
  value.to_s
132
- elsif [Integer, BigInteger].include?(type)
61
+ when "Integer"
133
62
  value.to_i
134
- elsif type == Float
63
+ when "Float"
135
64
  value.to_f
136
- elsif type == Date
65
+ when "Date"
137
66
  begin
138
67
  ::Date.parse(value.to_s)
139
68
  rescue ArgumentError
140
69
  nil
141
70
  end
142
- elsif type == DateTime
71
+ when "DateTime"
143
72
  DateTime.cast(value)
144
- elsif type == Time
73
+ when "Time"
145
74
  ::Time.parse(value.to_s)
146
- elsif type == TimeWithoutDate
75
+ when "TimeWithoutDate"
147
76
  TimeWithoutDate.cast(value)
148
- elsif type == Boolean
77
+ when "Boolean"
149
78
  to_boolean(value)
150
- elsif type == Decimal
79
+ when "Decimal"
151
80
  BigDecimal(value.to_s)
152
- elsif type == Hash
81
+ when "Hash"
153
82
  normalize_hash(Hash(value))
154
- elsif type == UUID
83
+ when "Uuid"
155
84
  UUID_REGEX.match?(value) ? value : SecureRandom.uuid
156
- elsif type == Symbol
85
+ when "Symbol"
157
86
  value.to_sym
158
- elsif type == Binary
87
+ when "Binary"
159
88
  value.force_encoding("BINARY")
160
- elsif type == URL
89
+ when "Url"
161
90
  URI.parse(value.to_s)
162
- elsif type == IPAddress
91
+ when "IpAddress"
163
92
  IPAddr.new(value.to_s)
164
- elsif type == JSON
165
- JSON.cast(value)
166
- # elsif type == Enum
167
- # value
93
+ when "Json"
94
+ Json.cast(value)
168
95
  else
169
96
  value
170
97
  end
@@ -173,21 +100,22 @@ module Lutaml
173
100
  def self.serialize(value, type)
174
101
  return if value.nil?
175
102
 
176
- if type == Date
103
+ case type.to_s.split("::").last
104
+ when "Date"
177
105
  value.iso8601
178
- elsif type == DateTime
106
+ when "DateTime"
179
107
  DateTime.serialize(value)
180
- elsif type == Integer
108
+ when "Integer"
181
109
  value.to_i
182
- elsif type == Float
110
+ when "Float"
183
111
  value.to_f
184
- elsif type == Boolean
112
+ when "Boolean"
185
113
  to_boolean(value)
186
- elsif type == Decimal
114
+ when "Decimal"
187
115
  value.to_s("F")
188
- elsif type == Hash
116
+ when "Hash"
189
117
  Hash(value)
190
- elsif type == JSON
118
+ when "Json"
191
119
  value.to_json
192
120
  else
193
121
  value.to_s
@@ -195,8 +123,13 @@ module Lutaml
195
123
  end
196
124
 
197
125
  def self.to_boolean(value)
198
- return true if value == true || value.to_s =~ (/^(true|t|yes|y|1)$/i)
199
- return false if value == false || value.nil? || value.to_s =~ (/^(false|f|no|n|0)$/i)
126
+ if value == true || value.to_s =~ (/^(true|t|yes|y|1)$/i)
127
+ return true
128
+ end
129
+
130
+ if value == false || value.nil? || value.to_s =~ (/^(false|f|no|n|0)$/i)
131
+ return false
132
+ end
200
133
 
201
134
  raise ArgumentError.new("invalid value for Boolean: \"#{value}\"")
202
135
  end
@@ -217,3 +150,7 @@ module Lutaml
217
150
  end
218
151
  end
219
152
  end
153
+
154
+ require_relative "type/time_without_date"
155
+ require_relative "type/date_time"
156
+ require_relative "type/json"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.3.0"
5
+ VERSION = "0.3.2"
6
6
  end
7
7
  end
@@ -1,11 +1,10 @@
1
- # lib/lutaml/model/xml_adapter/nokogiri_adapter.rb
2
1
  require "nokogiri"
3
- require_relative "../xml_adapter"
2
+ require_relative "xml_document"
4
3
 
5
4
  module Lutaml
6
5
  module Model
7
6
  module XmlAdapter
8
- class NokogiriDocument < Document
7
+ class NokogiriAdapter < XmlDocument
9
8
  def self.parse(xml)
10
9
  parsed = Nokogiri::XML(xml)
11
10
  root = NokogiriElement.new(parsed.root)
@@ -13,11 +12,12 @@ module Lutaml
13
12
  end
14
13
 
15
14
  def to_xml(options = {})
16
- builder = Nokogiri::XML::Builder.new do |xml|
15
+ builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
17
16
  if root.is_a?(Lutaml::Model::XmlAdapter::NokogiriElement)
18
17
  root.to_xml(xml)
19
18
  else
20
- options[:xml_attributes] = build_namespace_attributes(@root.class)
19
+ mapper_class = options[:mapper_class] || @root.class
20
+ options[:xml_attributes] = build_namespace_attributes(mapper_class)
21
21
  build_element(xml, @root, options)
22
22
  end
23
23
  end
@@ -32,7 +32,8 @@ module Lutaml
32
32
  private
33
33
 
34
34
  def build_unordered_element(xml, element, options = {})
35
- xml_mapping = element.class.mappings_for(:xml)
35
+ mapper_class = options[:mapper_class] || element.class
36
+ xml_mapping = mapper_class.mappings_for(:xml)
36
37
  return xml unless xml_mapping
37
38
 
38
39
  attributes = options[:xml_attributes] ||= {}
@@ -53,7 +54,7 @@ module Lutaml
53
54
  end
54
55
 
55
56
  xml_mapping.elements.each do |element_rule|
56
- attribute_def = attribute_definition_for(element, element_rule)
57
+ attribute_def = attribute_definition_for(element, element_rule, mapper_class: mapper_class)
57
58
  value = attribute_value_for(element, element_rule)
58
59
 
59
60
  next if value.nil? && !element_rule.render_nil?
@@ -79,7 +80,8 @@ module Lutaml
79
80
  end
80
81
 
81
82
  def build_ordered_element(xml, element, options = {})
82
- xml_mapping = element.class.mappings_for(:xml)
83
+ mapper_class = options[:mapper_class] || element.class
84
+ xml_mapping = mapper_class.mappings_for(:xml)
83
85
  return xml unless xml_mapping
84
86
 
85
87
  attributes = build_attributes(element, xml_mapping)&.compact
@@ -106,7 +108,7 @@ module Lutaml
106
108
  element_rule = xml_mapping.find_by_name(name)
107
109
  next if element_rule.nil?
108
110
 
109
- attribute_def = attribute_definition_for(element, element_rule)
111
+ attribute_def = attribute_definition_for(element, element_rule, mapper_class: mapper_class)
110
112
  value = attribute_value_for(element, element_rule)
111
113
  nsp_xml = element_rule.prefix ? xml[element_rule.prefix] : xml
112
114
 
@@ -127,7 +129,12 @@ module Lutaml
127
129
 
128
130
  def add_to_xml(xml, value, attribute, rule)
129
131
  if value && (attribute&.type&.<= Lutaml::Model::Serialize)
130
- handle_nested_elements(xml, value, rule)
132
+ handle_nested_elements(
133
+ xml,
134
+ value,
135
+ rule: rule,
136
+ attribute: attribute,
137
+ )
131
138
  else
132
139
  xml.public_send(rule.name) do
133
140
  if !value.nil?
@@ -146,11 +153,11 @@ module Lutaml
146
153
  end
147
154
  end
148
155
 
149
- class NokogiriElement < Element
156
+ class NokogiriElement < XmlElement
150
157
  def initialize(node, root_node: nil)
151
158
  if root_node
152
159
  node.namespaces.each do |prefix, name|
153
- namespace = Lutaml::Model::XmlNamespace.new(name, prefix)
160
+ namespace = XmlNamespace.new(name, prefix)
154
161
 
155
162
  root_node.add_namespace(namespace)
156
163
  end
@@ -164,7 +171,7 @@ module Lutaml
164
171
  attr.name
165
172
  end
166
173
 
167
- attributes[name] = Attribute.new(
174
+ attributes[name] = XmlAttribute.new(
168
175
  name,
169
176
  attr.value,
170
177
  namespace: attr.namespace&.href,
@@ -1,11 +1,10 @@
1
- # lib/lutaml/model/xml_adapter/oga_adapter.rb
2
1
  require "oga"
3
- require_relative "../xml_adapter"
2
+ require_relative "xml_document"
4
3
 
5
4
  module Lutaml
6
5
  module Model
7
6
  module XmlAdapter
8
- class OgaDocument < Document
7
+ class OgaAdapter < XmlDocument
9
8
  def self.parse(xml)
10
9
  parsed = Oga.parse_xml(xml)
11
10
  root = OgaElement.new(parsed)
@@ -51,10 +50,10 @@ module Lutaml
51
50
  end
52
51
  end
53
52
 
54
- class OgaElement < Element
53
+ class OgaElement < XmlElement
55
54
  def initialize(node)
56
55
  attributes = node.attributes.each_with_object({}) do |attr, hash|
57
- hash[attr.name] = Attribute.new(attr.name, attr.value)
56
+ hash[attr.name] = XmlAttribute.new(attr.name, attr.value)
58
57
  end
59
58
  super(node.name, attributes, parse_children(node), node.text)
60
59
  end
@@ -1,11 +1,10 @@
1
- # lib/lutaml/model/xml_adapter/ox_adapter.rb
2
1
  require "ox"
3
- require_relative "../xml_adapter"
2
+ require_relative "xml_document"
4
3
 
5
4
  module Lutaml
6
5
  module Model
7
6
  module XmlAdapter
8
- class OxDocument < Document
7
+ class OxAdapter < XmlDocument
9
8
  def self.parse(xml)
10
9
  parsed = Ox.parse(xml)
11
10
  root = OxElement.new(parsed)
@@ -14,9 +13,10 @@ module Lutaml
14
13
 
15
14
  def to_xml(options = {})
16
15
  builder = Ox::Builder.new
16
+
17
17
  if @root.is_a?(Lutaml::Model::XmlAdapter::OxElement)
18
18
  @root.to_xml(builder)
19
- elsif @root.ordered?
19
+ elsif ordered?(@root, options)
20
20
  build_ordered_element(builder, @root, options)
21
21
  else
22
22
  build_element(builder, @root, options)
@@ -30,7 +30,8 @@ module Lutaml
30
30
  private
31
31
 
32
32
  def build_unordered_element(builder, element, options = {})
33
- xml_mapping = element.class.mappings_for(:xml)
33
+ mapper_class = options[:mapper_class] || element.class
34
+ xml_mapping = mapper_class.mappings_for(:xml)
34
35
  return xml unless xml_mapping
35
36
 
36
37
  attributes = build_attributes(element, xml_mapping).compact
@@ -45,7 +46,7 @@ module Lutaml
45
46
 
46
47
  builder.element(prefixed_name, attributes) do |el|
47
48
  xml_mapping.elements.each do |element_rule|
48
- attribute_def = attribute_definition_for(element, element_rule)
49
+ attribute_def = attribute_definition_for(element, element_rule, mapper_class: mapper_class)
49
50
  value = attribute_value_for(element, element_rule)
50
51
 
51
52
  val = if attribute_def.collection?
@@ -58,7 +59,7 @@ module Lutaml
58
59
 
59
60
  val.each do |v|
60
61
  if attribute_def&.type&.<= Lutaml::Model::Serialize
61
- handle_nested_elements(el, v, element_rule)
62
+ handle_nested_elements(el, v, rule: element_rule, attribute: attribute_def)
62
63
  else
63
64
  builder.element(element_rule.prefixed_name) do |el|
64
65
  el.text(attribute_def.type.serialize(v)) if v
@@ -76,8 +77,9 @@ module Lutaml
76
77
  end
77
78
  end
78
79
 
79
- def build_ordered_element(builder, element, _options = {})
80
- xml_mapping = element.class.mappings_for(:xml)
80
+ def build_ordered_element(builder, element, options = {})
81
+ mapper_class = options[:mapper_class] || element.class
82
+ xml_mapping = mapper_class.mappings_for(:xml)
81
83
  return xml unless xml_mapping
82
84
 
83
85
  attributes = build_attributes(element, xml_mapping).compact
@@ -91,7 +93,7 @@ module Lutaml
91
93
 
92
94
  element_rule = xml_mapping.find_by_name(name)
93
95
 
94
- attribute_def = attribute_definition_for(element, element_rule)
96
+ attribute_def = attribute_definition_for(element, element_rule, mapper_class: mapper_class)
95
97
  value = attribute_value_for(element, element_rule)
96
98
 
97
99
  if element_rule == xml_mapping.content_mapping
@@ -110,7 +112,12 @@ module Lutaml
110
112
 
111
113
  def add_to_xml(xml, value, attribute, rule)
112
114
  if value && (attribute&.type&.<= Lutaml::Model::Serialize)
113
- handle_nested_elements(xml, value, rule)
115
+ handle_nested_elements(
116
+ xml,
117
+ value,
118
+ rule: rule,
119
+ attribute: attribute,
120
+ )
114
121
  else
115
122
  xml.element(rule.name) do |el|
116
123
  if !value.nil?
@@ -129,16 +136,16 @@ module Lutaml
129
136
  end
130
137
  end
131
138
 
132
- class OxElement < Element
139
+ class OxElement < XmlElement
133
140
  def initialize(node, root_node: nil)
134
141
  if node.is_a?(String)
135
142
  super("text", {}, [], node, parent_document: root_node)
136
143
  else
137
144
  namespace_attributes(node.attributes).each do |(name, value)|
138
145
  if root_node
139
- root_node.add_namespace(Lutaml::Model::XmlNamespace.new(value, name))
146
+ root_node.add_namespace(XmlNamespace.new(value, name))
140
147
  else
141
- add_namespace(Lutaml::Model::XmlNamespace.new(value, name))
148
+ add_namespace(XmlNamespace.new(value, name))
142
149
  end
143
150
  end
144
151
 
@@ -148,11 +155,11 @@ module Lutaml
148
155
  namespace_prefix = name.to_s.split(":").first
149
156
  if (n = name.to_s.split(":")).length > 1
150
157
  namespace = (root_node || self).namespaces[namespace_prefix]&.uri
151
- namespace ||= Lutaml::Model::XmlAdapter::XML_NAMESPACE_URI
158
+ namespace ||= XML_NAMESPACE_URI
152
159
  prefix = n.first
153
160
  end
154
161
 
155
- hash[name.to_s] = Attribute.new(
162
+ hash[name.to_s] = XmlAttribute.new(
156
163
  name.to_s,
157
164
  value,
158
165
  namespace: namespace,
@@ -165,7 +172,7 @@ module Lutaml
165
172
  attributes,
166
173
  parse_children(node, root_node: root_node || self),
167
174
  node.text,
168
- parent_document: root_node
175
+ parent_document: root_node,
169
176
  )
170
177
  end
171
178
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module XmlAdapter
6
+ # Represents an XML attribute
7
+ class XmlAttribute
8
+ attr_reader :name, :value, :namespace, :namespace_prefix
9
+
10
+ def initialize(name, value, namespace: nil, namespace_prefix: nil)
11
+ @name = name
12
+ @value = value
13
+ @namespace = namespace
14
+ @namespace_prefix = namespace_prefix
15
+ end
16
+
17
+ def unprefixed_name
18
+ if namespace_prefix
19
+ name.split(":").last
20
+ else
21
+ name
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end