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