lutaml-model 0.3.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
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