lutaml-model 0.1.0 → 0.3.0

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.
@@ -5,14 +5,12 @@ module Lutaml
5
5
  module Model
6
6
  module Schema
7
7
  class XsdSchema
8
- def self.generate(klass, options = {})
8
+ def self.generate(klass, _options = {})
9
9
  schema = Nokogiri::XML::Builder.new do |xml|
10
10
  xml.schema(xmlns: "http://www.w3.org/2001/XMLSchema") do
11
11
  xml.element(name: klass.name) do
12
12
  xml.complexType do
13
- xml.sequence do
14
- generate_elements(klass, xml)
15
- end
13
+ xml.sequence { generate_elements(klass, xml) }
16
14
  end
17
15
  end
18
16
  end
@@ -20,8 +18,6 @@ module Lutaml
20
18
  schema.to_xml
21
19
  end
22
20
 
23
- private
24
-
25
21
  def self.generate_elements(klass, xml)
26
22
  klass.attributes.each do |name, attr|
27
23
  xml.element(name: name, type: get_xsd_type(attr.type))
@@ -29,24 +25,15 @@ module Lutaml
29
25
  end
30
26
 
31
27
  def self.get_xsd_type(type)
32
- case type
33
- when Lutaml::Model::Type::String
34
- "xs:string"
35
- when Lutaml::Model::Type::Integer
36
- "xs:integer"
37
- when Lutaml::Model::Type::Boolean
38
- "xs:boolean"
39
- when Lutaml::Model::Type::Float
40
- "xs:float"
41
- when Lutaml::Model::Type::Decimal
42
- "xs:decimal"
43
- when Lutaml::Model::Type::Array
44
- "xs:array"
45
- when Lutaml::Model::Type::Hash
46
- "xs:object"
47
- else
48
- "xs:string" # Default to string for unknown types
49
- end
28
+ {
29
+ Lutaml::Model::Type::String => "xs:string",
30
+ Lutaml::Model::Type::Integer => "xs:integer",
31
+ Lutaml::Model::Type::Boolean => "xs:boolean",
32
+ Lutaml::Model::Type::Float => "xs:float",
33
+ Lutaml::Model::Type::Decimal => "xs:decimal",
34
+ Lutaml::Model::Type::Array => "xs:array",
35
+ Lutaml::Model::Type::Hash => "xs:object",
36
+ }[type] || "xs:string" # Default to string for unknown types
50
37
  end
51
38
  end
52
39
  end
@@ -5,7 +5,7 @@ module Lutaml
5
5
  module Model
6
6
  module Schema
7
7
  class YamlSchema
8
- def self.generate(klass, options = {})
8
+ def self.generate(klass, _options = {})
9
9
  schema = {
10
10
  "type" => "map",
11
11
  "mapping" => generate_mapping(klass),
@@ -13,8 +13,6 @@ module Lutaml
13
13
  YAML.dump(schema)
14
14
  end
15
15
 
16
- private
17
-
18
16
  def self.generate_mapping(klass)
19
17
  klass.attributes.each_with_object({}) do |(name, attr), mapping|
20
18
  mapping[name.to_s] = { "type" => get_yaml_type(attr.type) }
@@ -22,24 +20,16 @@ module Lutaml
22
20
  end
23
21
 
24
22
  def self.get_yaml_type(type)
25
- case type
26
- when Lutaml::Model::Type::String
27
- "str"
28
- when Lutaml::Model::Type::Integer
29
- "int"
30
- when Lutaml::Model::Type::Boolean
31
- "bool"
32
- when Lutaml::Model::Type::Float
33
- "float"
34
- when Lutaml::Model::Type::Decimal
35
- "float" # YAML does not have a separate decimal type, so we use float
36
- when Lutaml::Model::Type::Array
37
- "seq"
38
- when Lutaml::Model::Type::Hash
39
- "map"
40
- else
41
- "str" # Default to string for unknown types
42
- end
23
+ {
24
+ Lutaml::Model::Type::String => "str",
25
+ Lutaml::Model::Type::Integer => "int",
26
+ Lutaml::Model::Type::Boolean => "bool",
27
+ Lutaml::Model::Type::Float => "float",
28
+ # YAML does not have a separate decimal type, so we use float
29
+ Lutaml::Model::Type::Decimal => "float",
30
+ Lutaml::Model::Type::Array => "seq",
31
+ Lutaml::Model::Type::Hash => "map",
32
+ }[type] || "str" # Default to string for unknown types
43
33
  end
44
34
  end
45
35
  end
@@ -1,14 +1,11 @@
1
1
  # lib/lutaml/model/serialize.rb
2
- require_relative "json_adapter/standard"
3
- require_relative "json_adapter/multi_json"
4
2
  require_relative "yaml_adapter"
5
3
  require_relative "xml_adapter"
6
- require_relative "toml_adapter/toml_rb_adapter"
7
- require_relative "toml_adapter/tomlib_adapter"
8
4
  require_relative "config"
9
5
  require_relative "type"
10
6
  require_relative "attribute"
11
7
  require_relative "mapping_rule"
8
+ require_relative "mapping_hash"
12
9
  require_relative "xml_mapping"
13
10
  require_relative "key_value_mapping"
14
11
  require_relative "json_adapter"
@@ -25,17 +22,23 @@ module Lutaml
25
22
  module ClassMethods
26
23
  attr_accessor :attributes, :mappings
27
24
 
25
+ def inherited(subclass)
26
+ super
27
+
28
+ subclass.instance_variable_set(:@attributes, @attributes.dup)
29
+ end
30
+
28
31
  def attribute(name, type, options = {})
29
32
  self.attributes ||= {}
30
33
  attr = Attribute.new(name, type, options)
31
34
  attributes[name] = attr
32
35
 
33
36
  define_method(name) do
34
- instance_variable_get("@#{name}")
37
+ instance_variable_get(:"@#{name}")
35
38
  end
36
39
 
37
- define_method("#{name}=") do |value|
38
- instance_variable_set("@#{name}", value)
40
+ define_method(:"#{name}=") do |value|
41
+ instance_variable_set(:"@#{name}", value)
39
42
  end
40
43
  end
41
44
 
@@ -45,13 +48,17 @@ module Lutaml
45
48
  klass = format == :xml ? XmlMapping : KeyValueMapping
46
49
  self.mappings[format] = klass.new
47
50
  self.mappings[format].instance_eval(&block)
51
+
52
+ if format == :xml && !self.mappings[format].root_element
53
+ self.mappings[format].root(to_s)
54
+ end
48
55
  end
49
56
 
50
- define_method("from_#{format}") do |data|
51
- adapter = Lutaml::Model::Config.send("#{format}_adapter")
57
+ define_method(:"from_#{format}") do |data|
58
+ adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
52
59
  doc = adapter.parse(data)
53
60
  mapped_attrs = apply_mappings(doc.to_h, format)
54
- apply_content_mapping(doc, mapped_attrs) if format == :xml
61
+ # apply_content_mapping(doc, mapped_attrs) if format == :xml
55
62
  new(mapped_attrs)
56
63
  end
57
64
  end
@@ -64,55 +71,170 @@ module Lutaml
64
71
  klass = format == :xml ? XmlMapping : KeyValueMapping
65
72
  klass.new.tap do |mapping|
66
73
  attributes&.each do |name, attr|
67
- mapping.map_element(name.to_s, to: name, render_nil: attr.render_nil?)
74
+ mapping.map_element(name.to_s, to: name,
75
+ render_nil: attr.render_nil?)
68
76
  end
69
77
  end
70
78
  end
71
79
 
72
80
  def apply_mappings(doc, format)
81
+ return apply_xml_mapping(doc) if format == :xml
82
+
73
83
  mappings = mappings_for(format).mappings
74
- mappings.each_with_object({}) do |rule, hash|
75
- attr = attributes[rule.to]
84
+ mappings.each_with_object(Lutaml::Model::MappingHash.new) do |rule, hash|
85
+ attr = if rule.delegate
86
+ attributes[rule.delegate].type.attributes[rule.to]
87
+ else
88
+ attributes[rule.to]
89
+ end
90
+
76
91
  raise "Attribute '#{rule.to}' not found in #{self}" unless attr
77
92
 
78
- value = doc[rule.name]
93
+ value = if rule.custom_methods[:from]
94
+ new.send(rule.custom_methods[:from], hash, doc)
95
+ elsif doc.key?(rule.name) || doc.key?(rule.name.to_sym)
96
+ doc[rule.name] || doc[rule.name.to_sym]
97
+ else
98
+ attr.default
99
+ end
100
+
79
101
  if attr.collection?
80
- value = (value || []).map { |v| attr.type <= Serialize ? attr.type.new(v) : v }
81
- elsif value.is_a?(Hash) && attr.type <= Serialize
82
- value = attr.type.new(value)
102
+ value = (value || []).map do |v|
103
+ attr.type <= Serialize ? attr.type.apply_mappings(v, format) : v
104
+ end
105
+ elsif value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash
106
+ value = attr.type.apply_mappings(value, format)
107
+ end
108
+
109
+ if rule.delegate
110
+ hash[rule.delegate] ||= {}
111
+ hash[rule.delegate][rule.to] = value
112
+ else
113
+ hash[rule.to] = value
83
114
  end
84
- hash[rule.to] = value
85
115
  end
86
116
  end
87
117
 
88
- def apply_content_mapping(doc, mapped_attrs)
89
- content_mapping = mappings_for(:xml).content_mapping
90
- return unless content_mapping
118
+ def apply_xml_mapping(doc, caller_class: nil, mixed_content: false)
119
+ return unless doc
120
+
121
+ mappings = mappings_for(:xml).mappings
122
+
123
+ if doc.is_a?(Array)
124
+ raise "May be `collection: true` is" \
125
+ "missing for #{self} in #{caller_class}"
126
+ end
127
+
128
+ mapping_hash = Lutaml::Model::MappingHash.new
129
+ mapping_hash.item_order = doc.item_order
130
+ mapping_hash.ordered = mappings_for(:xml).mixed_content? || mixed_content
131
+
132
+ mappings.each_with_object(mapping_hash) do |rule, hash|
133
+ attr = attributes[rule.to]
134
+ raise "Attribute '#{rule.to}' not found in #{self}" unless attr
135
+
136
+ is_content_mapping = rule.name.nil?
137
+ value = if is_content_mapping
138
+ doc["text"]
139
+ else
140
+ doc[rule.name.to_s] || doc[rule.name.to_sym]
141
+ end
142
+
143
+ if attr.collection?
144
+ if value && !value.is_a?(Array)
145
+ value = [value]
146
+ end
147
+
148
+ value = (value || []).map do |v|
149
+ if attr.type <= Serialize
150
+ attr.type.apply_xml_mapping(v, caller_class: self, mixed_content: rule.mixed_content)
151
+ elsif v.is_a?(Hash)
152
+ v["text"]
153
+ else
154
+ v
155
+ end
156
+ end
157
+ elsif attr.type <= Serialize
158
+ value = attr.type.apply_xml_mapping(value, caller_class: self, mixed_content: rule.mixed_content)
159
+ else
160
+ if value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash
161
+ value = value["text"]
162
+ end
163
+
164
+ value = attr.type.cast(value) unless is_content_mapping
165
+ end
91
166
 
92
- content = doc.root.children.select(&:text?).map(&:text)
93
- mapped_attrs[content_mapping.to] = content
167
+ hash[rule.to] = value
168
+ end
94
169
  end
95
170
  end
96
171
 
172
+ attr_reader :element_order
173
+
97
174
  def initialize(attrs = {})
98
- return self unless self.class.attributes
175
+ return unless self.class.attributes
176
+
177
+ if attrs.is_a?(Lutaml::Model::MappingHash)
178
+ @ordered = attrs.ordered?
179
+ @element_order = attrs.item_order
180
+ end
99
181
 
100
182
  self.class.attributes.each do |name, attr|
101
- value = attrs.key?(name) ? attrs[name] : attr.default
102
- value = if attr.collection?
103
- (value || []).map { |v| Lutaml::Model::Type.cast(v, attr.type) }
183
+ value = attr_value(attrs, name, attr)
184
+
185
+ send(:"#{name}=", ensure_utf8(value))
186
+ end
187
+ end
188
+
189
+ def attr_value(attrs, name, attr_rule)
190
+ value = if attrs.key?(name)
191
+ attrs[name]
192
+ elsif attrs.key?(name.to_sym)
193
+ attrs[name.to_sym]
194
+ elsif attrs.key?(name.to_s)
195
+ attrs[name.to_s]
196
+ else
197
+ attr_rule.default
198
+ end
199
+
200
+ if attr_rule.collection? || value.is_a?(Array)
201
+ (value || []).map do |v|
202
+ if v.is_a?(Hash)
203
+ attr_rule.type.new(v)
104
204
  else
105
- Lutaml::Model::Type.cast(value, attr.type)
205
+ Lutaml::Model::Type.cast(
206
+ v, attr_rule.type
207
+ )
106
208
  end
107
- send("#{name}=", ensure_utf8(value))
209
+ end
210
+ elsif value.is_a?(Hash) && attr_rule.type != Lutaml::Model::Type::Hash
211
+ attr_rule.type.new(value)
212
+ else
213
+ Lutaml::Model::Type.cast(value, attr_rule.type)
108
214
  end
109
215
  end
110
216
 
217
+ def ordered?
218
+ @ordered
219
+ end
220
+
221
+ def key_exist?(hash, key)
222
+ hash.key?(key) || hash.key?(key.to_sym) || hash.key?(key.to_s)
223
+ end
224
+
225
+ def key_value(hash, key)
226
+ hash[key] || hash[key.to_sym] || hash[key.to_s]
227
+ end
228
+
111
229
  # TODO: Make this work
112
230
  # FORMATS.each do |format|
113
231
  # define_method("to_#{format}") do |options = {}|
114
232
  # adapter = Lutaml::Model::Config.send("#{format}_adapter")
115
- # representation = format == :yaml ? self : hash_representation(format, options)
233
+ # representation = if format == :yaml
234
+ # self
235
+ # else
236
+ # hash_representation(format, options)
237
+ # end
116
238
  # adapter.new(representation).send("to_#{format}", options)
117
239
  # end
118
240
  # end
@@ -148,20 +270,63 @@ module Lutaml
148
270
  name = rule.to
149
271
  next if except&.include?(name) || (only && !only.include?(name))
150
272
 
151
- value = send(name)
273
+ next handle_delegate(self, rule, hash) if rule.delegate
274
+
275
+ value = if rule.custom_methods[:to]
276
+ send(rule.custom_methods[:to], self, send(name))
277
+ else
278
+ send(name)
279
+ end
280
+
152
281
  next if value.nil? && !rule.render_nil
153
282
 
283
+ attribute = self.class.attributes[name]
284
+
154
285
  hash[rule.from] = case value
155
- when Array
156
- value.map { |v| v.is_a?(Serialize) ? v.hash_representation(format, options) : v }
157
- else
158
- value.is_a?(Serialize) ? value.hash_representation(format, options) : value
159
- end
286
+ when Array
287
+ value.map do |v|
288
+ if v.is_a?(Serialize)
289
+ v.hash_representation(format, options)
290
+ else
291
+ attribute.type.serialize(v)
292
+ end
293
+ end
294
+ else
295
+ if value.is_a?(Serialize)
296
+ value.hash_representation(format, options)
297
+ else
298
+ attribute.type.serialize(value)
299
+ end
300
+ end
160
301
  end
161
302
  end
162
303
 
163
304
  private
164
305
 
306
+ def handle_delegate(_obj, rule, hash)
307
+ name = rule.to
308
+ value = send(rule.delegate).send(name)
309
+ return if value.nil? && !rule.render_nil
310
+
311
+ attribute = send(rule.delegate).class.attributes[name]
312
+ hash[rule.from] = case value
313
+ when Array
314
+ value.map do |v|
315
+ if v.is_a?(Serialize)
316
+ v.hash_representation(format, options)
317
+ else
318
+ attribute.type.serialize(v)
319
+ end
320
+ end
321
+ else
322
+ if value.is_a?(Serialize)
323
+ value.hash_representation(format, options)
324
+ else
325
+ attribute.type.serialize(value)
326
+ end
327
+ end
328
+ end
329
+
165
330
  def ensure_utf8(value)
166
331
  case value
167
332
  when String
@@ -169,7 +334,9 @@ module Lutaml
169
334
  when Array
170
335
  value.map { |v| ensure_utf8(v) }
171
336
  when Hash
172
- value.transform_keys { |k| ensure_utf8(k) }.transform_values { |v| ensure_utf8(v) }
337
+ value.transform_keys do |k|
338
+ ensure_utf8(k)
339
+ end.transform_values { |v| ensure_utf8(v) }
173
340
  else
174
341
  value
175
342
  end
@@ -7,12 +7,13 @@ module Lutaml
7
7
  module TomlAdapter
8
8
  class TomlibDocument < Document
9
9
  def self.parse(toml)
10
- data = Tomlib::Parser.new(toml).parsed
10
+ data = Tomlib.load(toml)
11
11
  new(data)
12
12
  end
13
13
 
14
14
  def to_toml(*args)
15
- Tomlib::Generator.new(to_h).toml_str
15
+ Tomlib.dump(to_h, *args)
16
+ # Tomlib::Generator.new(to_h).toml_str
16
17
  end
17
18
  end
18
19
  end