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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +48 -9
- data/README.adoc +295 -55
- data/exe/lutaml-model +7 -0
- data/lib/lutaml/model/cli.rb +131 -0
- data/lib/lutaml/model/comparable_model.rb +528 -0
- data/lib/lutaml/model/comparable_nil.rb +11 -0
- data/lib/lutaml/model/comparison.rb +15 -0
- data/lib/lutaml/model/schema/json_schema.rb +23 -7
- data/lib/lutaml/model/schema/json_schema_parser.rb +91 -0
- data/lib/lutaml/model/schema/relaxng_schema.rb +42 -12
- data/lib/lutaml/model/schema/xsd_schema.rb +29 -11
- data/lib/lutaml/model/schema/yaml_schema.rb +21 -6
- data/lib/lutaml/model/serializable.rb +2 -0
- data/lib/lutaml/model/serialize.rb +23 -7
- data/lib/lutaml/model/type.rb +0 -8
- data/lib/lutaml/model/version.rb +1 -1
- data/lutaml-model.gemspec +2 -5
- metadata +25 -4
@@ -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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
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,
|
8
|
-
|
9
|
-
xml.
|
10
|
-
xml
|
11
|
-
|
12
|
-
|
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.
|
21
|
-
klass.
|
22
|
-
xml.element(name: name
|
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::
|
33
|
-
Lutaml::Model::Type::Hash => "
|
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,
|
8
|
-
|
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
|
11
|
-
|
12
|
-
|
13
|
-
|
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(
|
27
|
+
def self.generate_elements(xml, klass)
|
21
28
|
klass.attributes.each do |name, attr|
|
22
|
-
|
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::
|
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,
|
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] =
|
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
|
@@ -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
|
-
#
|
68
|
-
|
69
|
-
|
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
|
data/lib/lutaml/model/type.rb
CHANGED
@@ -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)
|
data/lib/lutaml/model/version.rb
CHANGED
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 = "
|
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
|
-
|
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.
|
4
|
+
version: 0.3.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ribose Inc.
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
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
|