lutaml-model 0.3.3 → 0.3.4

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.
@@ -17,12 +17,20 @@ module Lutaml
17
17
  end
18
18
 
19
19
  def self.generate_definitions(klass)
20
+ defs = { klass.name => generate_class_schema(klass) }
21
+ klass.attributes.each_value do |attr|
22
+ if attr.type <= Lutaml::Model::Serialize
23
+ defs.merge!(generate_definitions(attr.type))
24
+ end
25
+ end
26
+ defs
27
+ end
28
+
29
+ def self.generate_class_schema(klass)
20
30
  {
21
- klass.name => {
22
- "type" => "object",
23
- "properties" => generate_properties(klass),
24
- "required" => klass.attributes.keys,
25
- },
31
+ "type" => "object",
32
+ "properties" => generate_properties(klass),
33
+ "required" => klass.attributes.keys,
26
34
  }
27
35
  end
28
36
 
@@ -33,7 +41,16 @@ module Lutaml
33
41
  end
34
42
 
35
43
  def self.generate_property_schema(attr)
36
- { "type" => get_json_type(attr.type) }
44
+ if attr.type <= Lutaml::Model::Serialize
45
+ { "$ref" => "#/$defs/#{attr.type.name}" }
46
+ elsif attr.collection?
47
+ {
48
+ "type" => "array",
49
+ "items" => { "type" => get_json_type(attr.type) },
50
+ }
51
+ else
52
+ { "type" => get_json_type(attr.type) }
53
+ end
37
54
  end
38
55
 
39
56
  def self.get_json_type(type)
@@ -42,7 +59,6 @@ module Lutaml
42
59
  Lutaml::Model::Type::Integer => "integer",
43
60
  Lutaml::Model::Type::Boolean => "boolean",
44
61
  Lutaml::Model::Type::Float => "number",
45
- Lutaml::Model::Type::Array => "array",
46
62
  Lutaml::Model::Type::Hash => "object",
47
63
  }[type] || "string" # Default to string for unknown types
48
64
  end
@@ -0,0 +1,91 @@
1
+ require "json-schema"
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ class JsonSchemaParser
7
+ def self.parse(schema_json)
8
+ schema = JSON::Schema.parse(schema_json)
9
+ definitions = schema.schema.fetch("$defs", {})
10
+ definitions.map do |class_name, class_schema|
11
+ generate_class_definition(class_name, class_schema)
12
+ end.join("\n")
13
+ end
14
+
15
+ def self.generate_class_definition(class_name, class_schema)
16
+ attributes = class_schema["properties"] || {}
17
+ required_attributes = class_schema["required"] || []
18
+
19
+ <<~RUBY
20
+ class #{class_name} < Lutaml::Model::Serializable
21
+ #{generate_attributes(attributes, required_attributes)}
22
+
23
+ json do
24
+ #{generate_json_mappings(attributes)}
25
+ end
26
+ end
27
+ RUBY
28
+ end
29
+
30
+ def self.generate_attributes(attributes, required_attributes)
31
+ attributes.map do |name, schema|
32
+ type = schema["type"]
33
+ ruby_type = get_ruby_type(type, schema)
34
+ attributes = [
35
+ "attribute :#{name}",
36
+ "Lutaml::Model::Type::#{ruby_type}",
37
+ "required: #{required_attributes.include?(name).inspect}",
38
+ ]
39
+
40
+ attributes.join(", ")
41
+ end.join("\n ")
42
+ end
43
+
44
+ def self.generate_json_mappings(attributes)
45
+ attributes.keys.map do |name|
46
+ "map '#{name}', to: :#{name}"
47
+ end.join("\n ")
48
+ end
49
+
50
+ def self.get_ruby_type(type, schema)
51
+ case type
52
+ when "integer"
53
+ "Integer"
54
+ when "boolean"
55
+ "Boolean"
56
+ when "number"
57
+ "Float"
58
+ when "array"
59
+ item_schema = schema["items"]
60
+ item_type = get_ruby_type(item_schema["type"], item_schema)
61
+ "Array.of(#{item_type})"
62
+ when "object"
63
+ object_class_name(schema)
64
+ else
65
+ "String" # Default to string for unknown types
66
+ end
67
+ end
68
+
69
+ def self.object_class_name(schema)
70
+ nested_class_name = schema["title"] || "NestedObject"
71
+ nested_class_definition = generate_class_definition(
72
+ nested_class_name, schema
73
+ )
74
+ @nested_classes ||= []
75
+ @nested_classes << nested_class_definition
76
+ nested_class_name
77
+ end
78
+
79
+ def self.nested_classes
80
+ @nested_classes ||= []
81
+ end
82
+
83
+ def self.generate(schema_json)
84
+ @nested_classes = []
85
+ main_classes = parse(schema_json)
86
+ (nested_classes + [main_classes]).join("\n")
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -4,22 +4,52 @@ module Lutaml
4
4
  module Model
5
5
  module Schema
6
6
  class RelaxngSchema
7
- def self.generate(klass, _options = {})
8
- schema = Nokogiri::XML::Builder.new do |xml|
9
- xml.element(name: klass.name) do
10
- xml.complexType do
11
- xml.sequence do
12
- generate_elements(klass, xml)
7
+ def self.generate(klass, options = {})
8
+ builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
9
+ xml.grammar(xmlns: "http://relaxng.org/ns/structure/1.0") do
10
+ generate_start(xml, klass)
11
+ generate_define(xml, klass)
12
+ end
13
+ end
14
+
15
+ options[:pretty] ? builder.to_xml(indent: 2) : builder.to_xml
16
+ end
17
+
18
+ def self.generate_start(xml, klass)
19
+ xml.start do
20
+ xml.ref(name: klass.name)
21
+ end
22
+ end
23
+
24
+ def self.generate_attributes(xml, klass)
25
+ klass.attributes.each do |name, attr|
26
+ if attr.type <= Lutaml::Model::Serialize
27
+ xml.ref(name: attr.type.name)
28
+ elsif attr.collection?
29
+ xml.zeroOrMore do
30
+ xml.element(name: name) do
31
+ xml.data(type: get_relaxng_type(attr.type))
13
32
  end
14
33
  end
34
+ else
35
+ xml.element(name: name) do
36
+ xml.data(type: get_relaxng_type(attr.type))
37
+ end
15
38
  end
16
39
  end
17
- schema.to_xml
18
40
  end
19
41
 
20
- def self.generate_elements(klass, xml)
21
- klass.attributes.each do |name, attr|
22
- xml.element(name: name, type: get_relaxng_type(attr.type))
42
+ def self.generate_define(xml, klass)
43
+ xml.define(name: klass.name) do
44
+ xml.element(name: klass.name) do
45
+ generate_attributes(xml, klass)
46
+ end
47
+ end
48
+
49
+ klass.attributes.each_value do |attr|
50
+ if attr.type <= Lutaml::Model::Serialize
51
+ generate_define(xml, attr.type)
52
+ end
23
53
  end
24
54
  end
25
55
 
@@ -29,8 +59,8 @@ module Lutaml
29
59
  Lutaml::Model::Type::Integer => "integer",
30
60
  Lutaml::Model::Type::Boolean => "boolean",
31
61
  Lutaml::Model::Type::Float => "float",
32
- Lutaml::Model::Type::Array => "array",
33
- Lutaml::Model::Type::Hash => "object",
62
+ Lutaml::Model::Type::Decimal => "decimal",
63
+ Lutaml::Model::Type::Hash => "string",
34
64
  }[type] || "string" # Default to string for unknown types
35
65
  end
36
66
  end
@@ -4,22 +4,41 @@ module Lutaml
4
4
  module Model
5
5
  module Schema
6
6
  class XsdSchema
7
- def self.generate(klass, _options = {})
8
- schema = Nokogiri::XML::Builder.new do |xml|
7
+ def self.generate(klass, options = {})
8
+ builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
9
9
  xml.schema(xmlns: "http://www.w3.org/2001/XMLSchema") do
10
- xml.element(name: klass.name) do
11
- xml.complexType do
12
- xml.sequence { generate_elements(klass, xml) }
13
- end
10
+ generate_complex_type(xml, klass)
11
+ end
12
+ end
13
+
14
+ options[:pretty] ? builder.to_xml(indent: 2) : builder.to_xml
15
+ end
16
+
17
+ def self.generate_complex_type(xml, klass, element_name = nil)
18
+ xml.element(name: element_name || klass.name) do
19
+ xml.complexType do
20
+ xml.sequence do
21
+ generate_elements(xml, klass)
14
22
  end
15
23
  end
16
24
  end
17
- schema.to_xml
18
25
  end
19
26
 
20
- def self.generate_elements(klass, xml)
27
+ def self.generate_elements(xml, klass)
21
28
  klass.attributes.each do |name, attr|
22
- xml.element(name: name, type: get_xsd_type(attr.type))
29
+ if attr.type <= Lutaml::Model::Serialize
30
+ generate_complex_type(xml, attr.type, name)
31
+ elsif attr.collection?
32
+ xml.element(name: name, minOccurs: "0", maxOccurs: "unbounded") do
33
+ xml.complexType do
34
+ xml.sequence do
35
+ xml.element(name: "item", type: get_xsd_type(attr.type))
36
+ end
37
+ end
38
+ end
39
+ else
40
+ xml.element(name: name, type: get_xsd_type(attr.type))
41
+ end
23
42
  end
24
43
  end
25
44
 
@@ -30,8 +49,7 @@ module Lutaml
30
49
  Lutaml::Model::Type::Boolean => "xs:boolean",
31
50
  Lutaml::Model::Type::Float => "xs:float",
32
51
  Lutaml::Model::Type::Decimal => "xs:decimal",
33
- Lutaml::Model::Type::Array => "xs:array",
34
- Lutaml::Model::Type::Hash => "xs:object",
52
+ Lutaml::Model::Type::Hash => "xs:anyType",
35
53
  }[type] || "xs:string" # Default to string for unknown types
36
54
  end
37
55
  end
@@ -4,17 +4,34 @@ module Lutaml
4
4
  module Model
5
5
  module Schema
6
6
  class YamlSchema
7
- def self.generate(klass, _options = {})
8
- schema = {
7
+ def self.generate(klass, options = {})
8
+ schema = generate_schema(klass)
9
+ options[:pretty] ? schema.to_yaml : YAML.dump(schema)
10
+ end
11
+
12
+ def self.generate_schema(klass)
13
+ {
9
14
  "type" => "map",
10
15
  "mapping" => generate_mapping(klass),
11
16
  }
12
- YAML.dump(schema)
13
17
  end
14
18
 
15
19
  def self.generate_mapping(klass)
16
20
  klass.attributes.each_with_object({}) do |(name, attr), mapping|
17
- mapping[name.to_s] = { "type" => get_yaml_type(attr.type) }
21
+ mapping[name.to_s] = generate_attribute_schema(attr)
22
+ end
23
+ end
24
+
25
+ def self.generate_attribute_schema(attr)
26
+ if attr.type <= Lutaml::Model::Serialize
27
+ generate_schema(attr.type)
28
+ elsif attr.collection?
29
+ {
30
+ "type" => "seq",
31
+ "sequence" => [{ "type" => get_yaml_type(attr.type) }],
32
+ }
33
+ else
34
+ { "type" => get_yaml_type(attr.type) }
18
35
  end
19
36
  end
20
37
 
@@ -24,9 +41,7 @@ module Lutaml
24
41
  Lutaml::Model::Type::Integer => "int",
25
42
  Lutaml::Model::Type::Boolean => "bool",
26
43
  Lutaml::Model::Type::Float => "float",
27
- # YAML does not have a separate decimal type, so we use float
28
44
  Lutaml::Model::Type::Decimal => "float",
29
- Lutaml::Model::Type::Array => "seq",
30
45
  Lutaml::Model::Type::Hash => "map",
31
46
  }[type] || "str" # Default to string for unknown types
32
47
  end
@@ -7,3 +7,5 @@ module Lutaml
7
7
  end
8
8
  end
9
9
  end
10
+
11
+ require_relative "comparable_nil"
@@ -8,12 +8,15 @@ require_relative "mapping_hash"
8
8
  require_relative "xml_mapping"
9
9
  require_relative "key_value_mapping"
10
10
  require_relative "json_adapter"
11
+ require_relative "comparable_model"
11
12
 
12
13
  module Lutaml
13
14
  module Model
14
15
  module Serialize
15
16
  FORMATS = %i[xml json yaml toml].freeze
16
17
 
18
+ include ComparableModel
19
+
17
20
  def self.included(base)
18
21
  base.extend(ClassMethods)
19
22
  end
@@ -50,11 +53,8 @@ module Lutaml
50
53
  end
51
54
 
52
55
  define_method(:"#{name}=") do |value|
53
- unless self.class.attr_value_valid?(name, value)
54
- raise Lutaml::Model::InvalidValueError.new(name, value, options[:values])
55
- end
56
-
57
56
  instance_variable_set(:"@#{name}", value)
57
+ validate
58
58
  end
59
59
  end
60
60
 
@@ -64,9 +64,13 @@ module Lutaml
64
64
 
65
65
  return true unless attr.options[:values]
66
66
 
67
- # If value validation failed but there is a default value, do not
68
- # raise a validation error
69
- attr.options[:values].include?(value || attr.default)
67
+ # Allow nil values if there's no default
68
+ return true if value.nil? && !attr.default
69
+
70
+ # Use the default value if the value is nil
71
+ value = attr.default if value.nil?
72
+
73
+ attr.options[:values].include?(value)
70
74
  end
71
75
 
72
76
  FORMATS.each do |format|
@@ -403,6 +407,8 @@ module Lutaml
403
407
 
404
408
  send(:"#{name}=", self.class.ensure_utf8(value))
405
409
  end
410
+
411
+ validate
406
412
  end
407
413
 
408
414
  def ordered?
@@ -419,6 +425,7 @@ module Lutaml
419
425
 
420
426
  FORMATS.each do |format|
421
427
  define_method(:"to_#{format}") do |options = {}|
428
+ validate
422
429
  adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
423
430
  representation = if format == :xml
424
431
  self
@@ -429,6 +436,15 @@ module Lutaml
429
436
  adapter.new(representation).public_send(:"to_#{format}", options)
430
437
  end
431
438
  end
439
+
440
+ def validate
441
+ self.class.attributes.each do |name, attr|
442
+ value = send(name)
443
+ unless self.class.attr_value_valid?(name, value)
444
+ raise Lutaml::Model::InvalidValueError.new(name, value, attr.options[:values])
445
+ end
446
+ end
447
+ end
432
448
  end
433
449
  end
434
450
  end
@@ -42,14 +42,6 @@ module Lutaml
42
42
  HEREDOC
43
43
  end
44
44
 
45
- # TODO: Remove this. The XSD code depends on this but actually should
46
- # be converted into a collection, not a specific Array type.
47
- class Array
48
- def initialize(array)
49
- Array(array)
50
- end
51
- end
52
-
53
45
  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/
54
46
 
55
47
  def self.cast(value, type)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.3.3"
5
+ VERSION = "0.3.4"
6
6
  end
7
7
  end
data/lutaml-model.gemspec CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
16
16
  spec.homepage = "https://github.com/lutaml/lutaml-model"
17
17
  spec.license = "BSD-2-Clause"
18
18
 
19
- spec.bindir = "bin"
19
+ spec.bindir = "exe"
20
20
  spec.require_paths = ["lib"]
21
21
  spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
22
22
 
@@ -30,8 +30,5 @@ Gem::Specification.new do |spec|
30
30
  end
31
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
32
 
33
- # spec.add_runtime_dependency "expressir"
34
- # spec.add_runtime_dependency "metanorma-cli"
35
- # spec.add_runtime_dependency "shale"
36
- # spec.add_runtime_dependency "thor", ">= 0.20"
33
+ spec.add_dependency "thor"
37
34
  end
metadata CHANGED
@@ -1,21 +1,36 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
11
  date: 2024-08-16 00:00:00.000000000 Z
12
- dependencies: []
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description: 'LutaML creating data models in Ruby
14
28
 
15
29
  '
16
30
  email:
17
31
  - open.source@ribose.com
18
- executables: []
32
+ executables:
33
+ - lutaml-model
19
34
  extensions: []
20
35
  extra_rdoc_files: []
21
36
  files:
@@ -31,8 +46,13 @@ files:
31
46
  - Rakefile
32
47
  - bin/console
33
48
  - bin/setup
49
+ - exe/lutaml-model
34
50
  - lib/lutaml/model.rb
35
51
  - lib/lutaml/model/attribute.rb
52
+ - lib/lutaml/model/cli.rb
53
+ - lib/lutaml/model/comparable_model.rb
54
+ - lib/lutaml/model/comparable_nil.rb
55
+ - lib/lutaml/model/comparison.rb
36
56
  - lib/lutaml/model/config.rb
37
57
  - lib/lutaml/model/error.rb
38
58
  - lib/lutaml/model/error/invalid_value_error.rb
@@ -47,6 +67,7 @@ files:
47
67
  - lib/lutaml/model/mapping_rule.rb
48
68
  - lib/lutaml/model/schema.rb
49
69
  - lib/lutaml/model/schema/json_schema.rb
70
+ - lib/lutaml/model/schema/json_schema_parser.rb
50
71
  - lib/lutaml/model/schema/relaxng_schema.rb
51
72
  - lib/lutaml/model/schema/xsd_schema.rb
52
73
  - lib/lutaml/model/schema/yaml_schema.rb