lutaml-model 0.1.0 → 0.3.0

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