lutaml-model 0.7.3 → 0.7.5
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/.envrc +1 -0
- data/.github/workflows/release.yml +3 -0
- data/.gitignore +6 -1
- data/.irbrc +1 -0
- data/.pryrc +1 -0
- data/.rubocop_todo.yml +25 -52
- data/README.adoc +2177 -192
- data/docs/custom_registers.adoc +228 -0
- data/docs/schema_generation.adoc +898 -0
- data/docs/schema_import.adoc +364 -0
- data/flake.lock +114 -0
- data/flake.nix +103 -0
- data/lib/lutaml/model/attribute.rb +230 -94
- data/lib/lutaml/model/choice.rb +30 -0
- data/lib/lutaml/model/collection.rb +194 -0
- data/lib/lutaml/model/comparable_model.rb +3 -3
- data/lib/lutaml/model/config.rb +26 -3
- data/lib/lutaml/model/constants.rb +2 -0
- data/lib/lutaml/model/error/element_count_out_of_range_error.rb +29 -0
- data/lib/lutaml/model/error/invalid_attribute_name_error.rb +15 -0
- data/lib/lutaml/model/error/invalid_attribute_options_error.rb +16 -0
- data/lib/lutaml/model/error/invalid_choice_range_error.rb +3 -5
- data/lib/lutaml/model/error/register/not_registrable_class_error.rb +11 -0
- data/lib/lutaml/model/error/type/invalid_value_error.rb +5 -3
- data/lib/lutaml/model/error/type/max_bound_error.rb +20 -0
- data/lib/lutaml/model/error/type/max_length_error.rb +20 -0
- data/lib/lutaml/model/error/type/min_bound_error.rb +20 -0
- data/lib/lutaml/model/error/type/min_length_error.rb +20 -0
- data/lib/lutaml/model/error/type/pattern_not_matched_error.rb +18 -0
- data/lib/lutaml/model/error/validation_failed_error.rb +9 -0
- data/lib/lutaml/model/error.rb +10 -0
- data/lib/lutaml/model/errors.rb +36 -0
- data/lib/lutaml/model/format_registry.rb +5 -2
- data/lib/lutaml/model/global_register.rb +41 -0
- data/lib/lutaml/model/{hash.rb → hash_adapter.rb} +5 -5
- data/lib/lutaml/model/jsonl/document.rb +14 -0
- data/lib/lutaml/model/jsonl/mapping.rb +19 -0
- data/lib/lutaml/model/jsonl/mapping_rule.rb +9 -0
- data/lib/lutaml/model/jsonl/standard_adapter.rb +33 -0
- data/lib/lutaml/model/jsonl/transform.rb +19 -0
- data/lib/lutaml/model/jsonl.rb +21 -0
- data/lib/lutaml/model/key_value_document.rb +3 -2
- data/lib/lutaml/model/mapping/key_value_mapping.rb +64 -4
- data/lib/lutaml/model/mapping/key_value_mapping_rule.rb +4 -0
- data/lib/lutaml/model/mapping/mapping_rule.rb +8 -3
- data/lib/lutaml/model/register.rb +105 -0
- data/lib/lutaml/model/registrable.rb +6 -0
- data/lib/lutaml/model/schema/base_schema.rb +64 -0
- data/lib/lutaml/model/schema/decorators/attribute.rb +114 -0
- data/lib/lutaml/model/schema/decorators/choices.rb +31 -0
- data/lib/lutaml/model/schema/decorators/class_definition.rb +85 -0
- data/lib/lutaml/model/schema/decorators/definition_collection.rb +97 -0
- data/lib/lutaml/model/schema/generator/definition.rb +53 -0
- data/lib/lutaml/model/schema/generator/definitions_collection.rb +81 -0
- data/lib/lutaml/model/schema/generator/properties_collection.rb +63 -0
- data/lib/lutaml/model/schema/generator/property.rb +110 -0
- data/lib/lutaml/model/schema/generator/ref.rb +24 -0
- data/lib/lutaml/model/schema/helpers/template_helper.rb +49 -0
- data/lib/lutaml/model/schema/json_schema.rb +42 -49
- data/lib/lutaml/model/schema/relaxng_schema.rb +14 -10
- data/lib/lutaml/model/schema/renderer.rb +36 -0
- data/lib/lutaml/model/schema/shared_methods.rb +24 -0
- data/lib/lutaml/model/schema/templates/model.erb +9 -0
- data/lib/lutaml/model/schema/xml_compiler/attribute.rb +85 -0
- data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +45 -0
- data/lib/lutaml/model/schema/xml_compiler/choice.rb +65 -0
- data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +27 -0
- data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +34 -0
- data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +136 -0
- data/lib/lutaml/model/schema/xml_compiler/element.rb +104 -0
- data/lib/lutaml/model/schema/xml_compiler/group.rb +97 -0
- data/lib/lutaml/model/schema/xml_compiler/restriction.rb +101 -0
- data/lib/lutaml/model/schema/xml_compiler/sequence.rb +50 -0
- data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +36 -0
- data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +189 -0
- data/lib/lutaml/model/schema/xml_compiler.rb +231 -587
- data/lib/lutaml/model/schema/xsd_schema.rb +12 -8
- data/lib/lutaml/model/schema/yaml_schema.rb +41 -35
- data/lib/lutaml/model/schema.rb +1 -0
- data/lib/lutaml/model/sequence.rb +60 -30
- data/lib/lutaml/model/serialize.rb +175 -53
- data/lib/lutaml/model/services/base.rb +11 -0
- data/lib/lutaml/model/services/logger.rb +2 -2
- data/lib/lutaml/model/services/rule_value_extractor.rb +92 -0
- data/lib/lutaml/model/services/type/validator/number.rb +25 -0
- data/lib/lutaml/model/services/type/validator/string.rb +52 -0
- data/lib/lutaml/model/services/type/validator.rb +43 -0
- data/lib/lutaml/model/services/validator.rb +145 -0
- data/lib/lutaml/model/services.rb +3 -0
- data/lib/lutaml/model/transform/key_value_transform.rb +60 -50
- data/lib/lutaml/model/transform/xml_transform.rb +46 -57
- data/lib/lutaml/model/transform.rb +22 -8
- data/lib/lutaml/model/type/boolean.rb +1 -1
- data/lib/lutaml/model/type/date.rb +1 -1
- data/lib/lutaml/model/type/date_time.rb +1 -1
- data/lib/lutaml/model/type/decimal.rb +11 -9
- data/lib/lutaml/model/type/float.rb +2 -1
- data/lib/lutaml/model/type/integer.rb +24 -21
- data/lib/lutaml/model/type/string.rb +4 -2
- data/lib/lutaml/model/type/time.rb +1 -1
- data/lib/lutaml/model/type/time_without_date.rb +1 -1
- data/lib/lutaml/model/type/value.rb +5 -1
- data/lib/lutaml/model/type.rb +5 -2
- data/lib/lutaml/model/utils.rb +30 -8
- data/lib/lutaml/model/validation.rb +6 -4
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml/document.rb +37 -19
- data/lib/lutaml/model/xml/mapping.rb +74 -13
- data/lib/lutaml/model/xml/mapping_rule.rb +10 -2
- data/lib/lutaml/model/xml/nokogiri_adapter.rb +5 -3
- data/lib/lutaml/model/xml/oga/element.rb +4 -1
- data/lib/lutaml/model/xml/oga_adapter.rb +4 -3
- data/lib/lutaml/model/xml/ox_adapter.rb +20 -6
- data/lib/lutaml/model/xml/xml_element.rb +3 -28
- data/lib/lutaml/model/xml_adapter/element.rb +1 -1
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +1 -1
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +1 -1
- data/lib/lutaml/model/yamls/document.rb +14 -0
- data/lib/lutaml/model/yamls/mapping.rb +19 -0
- data/lib/lutaml/model/yamls/mapping_rule.rb +9 -0
- data/lib/lutaml/model/yamls/standard_adapter.rb +34 -0
- data/lib/lutaml/model/yamls/transform.rb +19 -0
- data/lib/lutaml/model/yamls.rb +21 -0
- data/lib/lutaml/model.rb +7 -31
- data/spec/benchmarks/xml_parsing_benchmark_spec.rb +4 -5
- data/spec/fixtures/xml/advanced_test_schema.xsd +134 -0
- data/spec/fixtures/xml/examples/nested_categories.xml +55 -0
- data/spec/fixtures/xml/examples/valid_catalog.xml +43 -0
- data/spec/fixtures/xml/product_catalog.xsd +151 -0
- data/spec/fixtures/xml/specifications_schema.xsd +38 -0
- data/spec/lutaml/model/attribute_collection_spec.rb +101 -0
- data/spec/lutaml/model/attribute_spec.rb +41 -44
- data/spec/lutaml/model/choice_spec.rb +44 -0
- data/spec/lutaml/model/custom_collection_spec.rb +830 -0
- data/spec/lutaml/model/custom_model_spec.rb +15 -3
- data/spec/lutaml/model/defaults_spec.rb +5 -1
- data/spec/lutaml/model/global_register_spec.rb +108 -0
- data/spec/lutaml/model/group_spec.rb +9 -3
- data/spec/lutaml/model/jsonl/standard_adapter_spec.rb +91 -0
- data/spec/lutaml/model/jsonl_spec.rb +229 -0
- data/spec/lutaml/model/multiple_mapping_spec.rb +1 -1
- data/spec/lutaml/model/register/key_value_spec.rb +275 -0
- data/spec/lutaml/model/register/xml_spec.rb +187 -0
- data/spec/lutaml/model/register_spec.rb +147 -0
- data/spec/lutaml/model/rule_value_extractor_spec.rb +162 -0
- data/spec/lutaml/model/schema/generator/definitions_collection_spec.rb +120 -0
- data/spec/lutaml/model/schema/json_schema_spec.rb +412 -51
- data/spec/lutaml/model/schema/json_schema_to_models_spec.rb +383 -0
- data/spec/lutaml/model/schema/xml_compiler/attribute_group_spec.rb +65 -0
- data/spec/lutaml/model/schema/xml_compiler/attribute_spec.rb +63 -0
- data/spec/lutaml/model/schema/xml_compiler/choice_spec.rb +71 -0
- data/spec/lutaml/model/schema/xml_compiler/complex_content_restriction_spec.rb +55 -0
- data/spec/lutaml/model/schema/xml_compiler/complex_content_spec.rb +37 -0
- data/spec/lutaml/model/schema/xml_compiler/complex_type_spec.rb +173 -0
- data/spec/lutaml/model/schema/xml_compiler/element_spec.rb +63 -0
- data/spec/lutaml/model/schema/xml_compiler/group_spec.rb +86 -0
- data/spec/lutaml/model/schema/xml_compiler/restriction_spec.rb +76 -0
- data/spec/lutaml/model/schema/xml_compiler/sequence_spec.rb +59 -0
- data/spec/lutaml/model/schema/xml_compiler/simple_content_spec.rb +55 -0
- data/spec/lutaml/model/schema/xml_compiler/simple_type_spec.rb +181 -0
- data/spec/lutaml/model/schema/xml_compiler_spec.rb +503 -1804
- data/spec/lutaml/model/schema/yaml_schema_spec.rb +249 -26
- data/spec/lutaml/model/sequence_spec.rb +36 -0
- data/spec/lutaml/model/serializable_spec.rb +31 -0
- data/spec/lutaml/model/type_spec.rb +8 -4
- data/spec/lutaml/model/utils_spec.rb +3 -3
- data/spec/lutaml/model/xml/derived_attributes_spec.rb +1 -1
- data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
- data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
- data/spec/lutaml/model/xml_adapter_spec.rb +24 -0
- data/spec/lutaml/model/xml_mapping_rule_spec.rb +11 -4
- data/spec/lutaml/model/xml_mapping_spec.rb +1 -1
- data/spec/lutaml/model/yamls/standard_adapter_spec.rb +183 -0
- data/spec/lutaml/model/yamls_spec.rb +294 -0
- data/spec/spec_helper.rb +1 -0
- metadata +105 -9
- data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
- /data/lib/lutaml/model/{hash → hash_adapter}/document.rb +0 -0
- /data/lib/lutaml/model/{hash → hash_adapter}/mapping.rb +0 -0
- /data/lib/lutaml/model/{hash → hash_adapter}/mapping_rule.rb +0 -0
- /data/lib/lutaml/model/{hash → hash_adapter}/standard_adapter.rb +0 -0
- /data/lib/lutaml/model/{hash → hash_adapter}/transform.rb +0 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "attribute"
|
4
|
+
require_relative "choices"
|
5
|
+
|
6
|
+
module Lutaml
|
7
|
+
module Model
|
8
|
+
module Schema
|
9
|
+
module Decorators
|
10
|
+
class ClassDefinition
|
11
|
+
attr_accessor :base_class, :sub_classes
|
12
|
+
attr_reader :additional_properties, :properties, :namespaced_name
|
13
|
+
|
14
|
+
# Decorates a JSON schema information to be used in class definitions.
|
15
|
+
# This class is used to provide a structured way to handle schema
|
16
|
+
# information for class definitions, including attributes,
|
17
|
+
# required fields, and JSON mappings.
|
18
|
+
#
|
19
|
+
# @param schema [Hash] The JSON schema to be decorated.
|
20
|
+
# @param options [Hash] Additional options for the decorator.
|
21
|
+
def initialize(namespaced_name, schema)
|
22
|
+
@namespaced_name = namespaced_name
|
23
|
+
@choices = schema["oneOf"] || []
|
24
|
+
@additional_properties = schema["additionalProperties"] || false
|
25
|
+
@polymorphic_attributes = []
|
26
|
+
|
27
|
+
@properties = (schema["properties"] || {}).to_h do |name, attr|
|
28
|
+
attribute = Decorators::Attribute.new(name, attr)
|
29
|
+
polymorphic_attributes << attribute if attribute.polymorphic?
|
30
|
+
|
31
|
+
[name, attribute]
|
32
|
+
end
|
33
|
+
|
34
|
+
@base_class = nil
|
35
|
+
@sub_classes = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def name
|
39
|
+
@name ||= @namespaced_name.split("_").last
|
40
|
+
end
|
41
|
+
|
42
|
+
def namespaces
|
43
|
+
@namespaces ||= @namespaced_name.split("_")[0..-2]
|
44
|
+
end
|
45
|
+
|
46
|
+
def parent_class
|
47
|
+
@parent_class ||= @base_class&.namespaced_name&.gsub("_", "::") || "Lutaml::Model::Serializable"
|
48
|
+
end
|
49
|
+
|
50
|
+
def choice?
|
51
|
+
@choices&.any?
|
52
|
+
end
|
53
|
+
|
54
|
+
def attributes
|
55
|
+
return @properties.values if !choice?
|
56
|
+
|
57
|
+
choice_attributes = {}
|
58
|
+
@choices.each do |choice|
|
59
|
+
choice["properties"].each do |name, attr|
|
60
|
+
choice_attributes[name] = properties[name] || Decorators::Attribute.new(name, attr)
|
61
|
+
properties[name] = nil if properties[name]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
@properties.values.compact + [Choices.new(choice_attributes)]
|
66
|
+
end
|
67
|
+
|
68
|
+
def polymorphic?
|
69
|
+
polymorphic_attributes.any?
|
70
|
+
end
|
71
|
+
|
72
|
+
def polymorphic_attributes
|
73
|
+
return @polymorphic_attributes if !@polymorphic_attributes.nil?
|
74
|
+
|
75
|
+
attributes.each do |attr|
|
76
|
+
@polymorphic_attributes << attr if attr.polymorphic?
|
77
|
+
end
|
78
|
+
|
79
|
+
@polymorphic_attributes
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "class_definition"
|
4
|
+
|
5
|
+
module Lutaml
|
6
|
+
module Model
|
7
|
+
module Schema
|
8
|
+
module Decorators
|
9
|
+
class DefinitionCollection
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
def initialize(definitions_hash)
|
13
|
+
@definition_hash = definitions_hash
|
14
|
+
@polymorphic_classes = []
|
15
|
+
|
16
|
+
@definitions = definitions_hash.to_h do |name, schema|
|
17
|
+
definition = definition_class.new(name, schema)
|
18
|
+
@polymorphic_classes << definition if definition.polymorphic?
|
19
|
+
|
20
|
+
[name, definition]
|
21
|
+
end
|
22
|
+
|
23
|
+
resolve_polymorphic_base_types!
|
24
|
+
end
|
25
|
+
|
26
|
+
def each(&block)
|
27
|
+
@definitions.each(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def transform_values(&block)
|
31
|
+
@definitions.transform_values(&block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](name)
|
35
|
+
@definitions[name] || raise("Definition not found: #{name}")
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def definition_class
|
41
|
+
Lutaml::Model::Schema::Decorators::ClassDefinition
|
42
|
+
end
|
43
|
+
|
44
|
+
def resolve_polymorphic_base_types!
|
45
|
+
@polymorphic_classes.each do |poly_class|
|
46
|
+
poly_class.polymorphic_attributes.each do |attr|
|
47
|
+
resolve_polymorphic_attribute(attr)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def resolve_polymorphic_attribute(attr)
|
53
|
+
child_classes = resolve_child_classes(attr.polymorphic)
|
54
|
+
return if child_classes.size < 2
|
55
|
+
|
56
|
+
base_class = find_common_base_class(child_classes)
|
57
|
+
return unless base_class
|
58
|
+
|
59
|
+
update_polymorphic_attribute(attr, base_class)
|
60
|
+
refactor_subclasses(child_classes, base_class)
|
61
|
+
end
|
62
|
+
|
63
|
+
def resolve_child_classes(class_names)
|
64
|
+
class_names.filter_map { |name| @definitions[name] }
|
65
|
+
end
|
66
|
+
|
67
|
+
def update_polymorphic_attribute(attr, base_class)
|
68
|
+
attr.base_class = base_class
|
69
|
+
attr.type = base_class.namespaced_name.gsub("_", "::")
|
70
|
+
end
|
71
|
+
|
72
|
+
def refactor_subclasses(child_classes, base_class)
|
73
|
+
common_keys = base_class.properties.keys.to_set
|
74
|
+
(child_classes - [base_class]).each do |child|
|
75
|
+
child.base_class = base_class
|
76
|
+
child.properties.reject! { |key, _| common_keys.include?(key) }
|
77
|
+
base_class.sub_classes << child unless base_class.sub_classes.include?(child)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def find_common_base_class(class_defs)
|
82
|
+
return nil if class_defs.size < 2
|
83
|
+
|
84
|
+
# Get intersection of all property names
|
85
|
+
common_props = class_defs.map { |klass| klass.properties.keys }.reduce(:&)
|
86
|
+
return nil if common_props.empty?
|
87
|
+
|
88
|
+
# Look for a class that exactly matches the common properties — assume it's the base
|
89
|
+
class_defs.find do |klass|
|
90
|
+
klass.properties.keys.to_set == common_props.to_set
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require_relative "properties_collection"
|
2
|
+
require_relative "../shared_methods"
|
3
|
+
|
4
|
+
module Lutaml
|
5
|
+
module Model
|
6
|
+
module Schema
|
7
|
+
module Generator
|
8
|
+
class Definition
|
9
|
+
include SharedMethods
|
10
|
+
|
11
|
+
attr_reader :type, :name
|
12
|
+
|
13
|
+
def initialize(type)
|
14
|
+
@type = type
|
15
|
+
@name = type.name.gsub("::", "_")
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_schema
|
19
|
+
@schema = {
|
20
|
+
name => {
|
21
|
+
"type" => "object",
|
22
|
+
"additionalProperties" => false,
|
23
|
+
"properties" => properties_to_schema(type),
|
24
|
+
},
|
25
|
+
}
|
26
|
+
|
27
|
+
# Add choice validation if present
|
28
|
+
if type.choice_attributes.any?
|
29
|
+
@schema[name]["oneOf"] = generate_choice_attributes(type)
|
30
|
+
end
|
31
|
+
|
32
|
+
@schema
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def generate_choice_attributes(type)
|
38
|
+
type.choice_attributes.map do |choice|
|
39
|
+
{
|
40
|
+
"type" => "object",
|
41
|
+
"properties" => PropertiesCollection.from_attributes(choice.attributes, extract_register_from(type)).to_schema,
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def properties_to_schema(type)
|
47
|
+
PropertiesCollection.from_class(type).to_schema
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require_relative "definition"
|
2
|
+
require_relative "../shared_methods"
|
3
|
+
|
4
|
+
module Lutaml
|
5
|
+
module Model
|
6
|
+
module Schema
|
7
|
+
module Generator
|
8
|
+
class DefinitionsCollection
|
9
|
+
class << self
|
10
|
+
include SharedMethods
|
11
|
+
|
12
|
+
def from_class(klass)
|
13
|
+
new.tap do |collection|
|
14
|
+
collection << Definition.new(klass)
|
15
|
+
|
16
|
+
process_attributes(collection, klass)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def process_attributes(collection, klass)
|
21
|
+
register = extract_register_from(klass)
|
22
|
+
klass.attributes.each_value do |attribute|
|
23
|
+
next unless attribute.serializable?(register)
|
24
|
+
|
25
|
+
process_attribute(collection, attribute, register)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def process_attribute(collection, attribute, register)
|
30
|
+
attr_type = Lutaml::Model::GlobalRegister.lookup(register).get_class(attribute.type)
|
31
|
+
collection.merge(DefinitionsCollection.from_class(attr_type))
|
32
|
+
|
33
|
+
process_polymorphic_types(collection, attribute)
|
34
|
+
end
|
35
|
+
|
36
|
+
def process_polymorphic_types(collection, attribute)
|
37
|
+
return unless attribute.options&.[](:polymorphic)
|
38
|
+
|
39
|
+
attribute.options[:polymorphic].each do |child|
|
40
|
+
collection.merge(DefinitionsCollection.from_class(child))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :definitions
|
46
|
+
|
47
|
+
def initialize(definitions = [])
|
48
|
+
@definitions = definitions.map do |definition|
|
49
|
+
next definition if definition.is_a?(Definition)
|
50
|
+
|
51
|
+
Definition.new(definition)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_schema
|
56
|
+
definitions.each_with_object({}) do |definition, schema|
|
57
|
+
schema.merge!(definition.to_schema)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_definition(definition)
|
62
|
+
@definitions ||= []
|
63
|
+
@definitions << definition
|
64
|
+
end
|
65
|
+
alias << add_definition
|
66
|
+
alias push add_definition
|
67
|
+
|
68
|
+
def merge(collection)
|
69
|
+
@definitions ||= []
|
70
|
+
|
71
|
+
if collection.is_a?(Array)
|
72
|
+
@definitions.concat(collection)
|
73
|
+
else
|
74
|
+
@definitions.concat(collection.definitions)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require_relative "property"
|
2
|
+
require_relative "../shared_methods"
|
3
|
+
|
4
|
+
module Lutaml
|
5
|
+
module Model
|
6
|
+
module Schema
|
7
|
+
module Generator
|
8
|
+
class PropertiesCollection
|
9
|
+
class << self
|
10
|
+
include SharedMethods
|
11
|
+
|
12
|
+
def from_class(klass)
|
13
|
+
from_attributes(klass.attributes.values, extract_register_from(klass))
|
14
|
+
end
|
15
|
+
|
16
|
+
def from_attributes(attributes, register)
|
17
|
+
new(register: register).tap do |collection|
|
18
|
+
attributes.each do |attribute|
|
19
|
+
name = attribute.name
|
20
|
+
collection << Property.new(name, attribute, register: register)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :properties, :register
|
27
|
+
|
28
|
+
def initialize(properties = [], register:)
|
29
|
+
self.properties = properties
|
30
|
+
@register = register
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_schema
|
34
|
+
properties.each_with_object({}) do |property, schema|
|
35
|
+
schema.merge!(property.to_schema)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_property(property)
|
40
|
+
@properties << if property.is_a?(Property)
|
41
|
+
property
|
42
|
+
else
|
43
|
+
Property.new(property.name, property, register: register)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
alias << add_property
|
47
|
+
alias push add_property
|
48
|
+
|
49
|
+
def properties=(properties)
|
50
|
+
@properties ||= []
|
51
|
+
@properties.clear
|
52
|
+
|
53
|
+
@properties = properties.map do |property|
|
54
|
+
next property if property.is_a?(Property)
|
55
|
+
|
56
|
+
Property.new(property.name, property, register)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Lutaml
|
2
|
+
module Model
|
3
|
+
module Schema
|
4
|
+
module Generator
|
5
|
+
# This class is used to generate a property schema definition.
|
6
|
+
# It is used in the context of generating JSON schemas.
|
7
|
+
class Property
|
8
|
+
attr_reader :name, :attribute, :register
|
9
|
+
|
10
|
+
def initialize(name, attribute, register:)
|
11
|
+
@name = name.to_s.gsub("::", "_")
|
12
|
+
@attribute = attribute
|
13
|
+
@register = register
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_schema
|
17
|
+
{ name => generate_attribute_schema(attribute) }
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def generate_attribute_schema(attr, options = {})
|
23
|
+
include_null = options.fetch(:include_null, true)
|
24
|
+
inside_collection = options.fetch(:inside_collection, false)
|
25
|
+
if attr.collection? && !inside_collection
|
26
|
+
collection_schema(attr)
|
27
|
+
elsif attr.serializable?(register) && polymorphic?(attr)
|
28
|
+
polymorphic_schema(attr)
|
29
|
+
elsif attr.serializable?(register)
|
30
|
+
Generator::Ref.new(attr.type(register)).to_schema
|
31
|
+
else
|
32
|
+
primitive_schema(attr, include_null: include_null)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def collection_schema(attr)
|
37
|
+
schema = {
|
38
|
+
"type" => "array",
|
39
|
+
"items" => generate_attribute_schema(
|
40
|
+
attr,
|
41
|
+
include_null: false,
|
42
|
+
inside_collection: true,
|
43
|
+
),
|
44
|
+
}
|
45
|
+
|
46
|
+
if attr.options[:collection].is_a?(Range)
|
47
|
+
add_collection_constraints!(schema, attr.options[:collection])
|
48
|
+
end
|
49
|
+
|
50
|
+
schema
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_collection_constraints!(schema, range)
|
54
|
+
schema["minItems"] = range.begin
|
55
|
+
schema["maxItems"] = range.end if range.end
|
56
|
+
end
|
57
|
+
|
58
|
+
def polymorphic_schema(attr)
|
59
|
+
ref_schemas = attr.options[:polymorphic].map do |type|
|
60
|
+
Ref.new(type).to_schema
|
61
|
+
end
|
62
|
+
|
63
|
+
ref_schemas << Ref.new(attr.type).to_schema if attr.type
|
64
|
+
|
65
|
+
{
|
66
|
+
"type" => ["object", "null"],
|
67
|
+
"oneOf" => ref_schemas,
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def primitive_schema(attr, include_null: true)
|
72
|
+
type = get_type(attr.type(register))
|
73
|
+
type = [type, "null"] if include_null
|
74
|
+
|
75
|
+
{ "type" => type }.merge(get_constraints(attr))
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_constraints(attr)
|
79
|
+
constraints = {}
|
80
|
+
|
81
|
+
# Add pattern validation
|
82
|
+
constraints["pattern"] = attr.pattern.source if attr.pattern
|
83
|
+
|
84
|
+
# Add default value
|
85
|
+
constraints["default"] = attr.default(register) if attr.default_set?(register)
|
86
|
+
|
87
|
+
# Add enumeration values
|
88
|
+
constraints["enum"] = attr.enum_values if attr.enum?
|
89
|
+
|
90
|
+
constraints
|
91
|
+
end
|
92
|
+
|
93
|
+
def polymorphic?(attr)
|
94
|
+
Utils.present?(attr.options[:polymorphic])
|
95
|
+
end
|
96
|
+
|
97
|
+
def get_type(type)
|
98
|
+
{
|
99
|
+
Lutaml::Model::Type::String => "string",
|
100
|
+
Lutaml::Model::Type::Integer => "integer",
|
101
|
+
Lutaml::Model::Type::Boolean => "boolean",
|
102
|
+
Lutaml::Model::Type::Float => "number",
|
103
|
+
Lutaml::Model::Type::Hash => "object",
|
104
|
+
}[type] || "string" # Default to string for unknown types
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Lutaml
|
2
|
+
module Model
|
3
|
+
module Schema
|
4
|
+
module Generator
|
5
|
+
# This class is used to generate a reference to a schema definition.
|
6
|
+
# It is used in the context of generating JSON schemas.
|
7
|
+
class Ref
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
def initialize(type)
|
11
|
+
@type = type
|
12
|
+
@name = @type.name.gsub("::", "_")
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_schema
|
16
|
+
{
|
17
|
+
"$ref" => "#/$defs/#{name}",
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Lutaml
|
2
|
+
module Model
|
3
|
+
module Schema
|
4
|
+
module Helpers
|
5
|
+
module TemplateHelper
|
6
|
+
def open_namespaces(namespaces)
|
7
|
+
namespaces.map { |ns| "module #{ns}" }.join("\n")
|
8
|
+
end
|
9
|
+
|
10
|
+
def close_namespaces(namespaces)
|
11
|
+
namespaces.reverse.map { "end" }.join("\n")
|
12
|
+
end
|
13
|
+
|
14
|
+
def indent(level)
|
15
|
+
" " * level
|
16
|
+
end
|
17
|
+
|
18
|
+
def attribute_line(attribute, level)
|
19
|
+
if attribute.choice?
|
20
|
+
<<~RUBY.chomp
|
21
|
+
|
22
|
+
#{indent(level)}choice do
|
23
|
+
#{attribute.attributes.map { |attr| attribute_line(attr, level + 1) }.join("\n")}
|
24
|
+
#{indent(level)}end
|
25
|
+
RUBY
|
26
|
+
else
|
27
|
+
"#{indent(level)}attribute #{attribute_properties(attribute)}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def attribute_properties(attribute)
|
32
|
+
# properties = {}
|
33
|
+
# properties[:default] = attribute.default if attribute.default
|
34
|
+
# properties[:collection] = true if attribute.collection?
|
35
|
+
|
36
|
+
required_properties = ":#{attribute.name}, #{attribute.type}"
|
37
|
+
required_properties += ", default: #{attribute.default.inspect}" if attribute.default
|
38
|
+
required_properties += ", collection: #{attribute.collection}" if attribute.collection?
|
39
|
+
required_properties += ", values: #{attribute.options['enum']}" if attribute.options["enum"]
|
40
|
+
required_properties += ", pattern: /#{attribute.options['pattern']}/" if attribute.options["pattern"]
|
41
|
+
required_properties += ", polymorphic: [#{attribute.polymorphic_classes.join(', ')}]" if attribute.polymorphic?
|
42
|
+
|
43
|
+
required_properties
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,66 +1,59 @@
|
|
1
|
+
require_relative "shared_methods"
|
2
|
+
require_relative "base_schema"
|
3
|
+
require_relative "renderer"
|
4
|
+
require_relative "decorators/class_definition"
|
5
|
+
require_relative "decorators/definition_collection"
|
6
|
+
|
1
7
|
require "json"
|
2
8
|
|
3
9
|
module Lutaml
|
4
10
|
module Model
|
5
11
|
module Schema
|
6
|
-
class JsonSchema
|
7
|
-
|
8
|
-
|
9
|
-
"$schema" => "https://json-schema.org/draft/2020-12/schema",
|
10
|
-
"$id" => options[:id],
|
11
|
-
"description" => options[:description],
|
12
|
-
"$ref" => "#/$defs/#{klass.name}",
|
13
|
-
"$defs" => generate_definitions(klass),
|
14
|
-
}.compact
|
12
|
+
class JsonSchema < BaseSchema
|
13
|
+
class << self
|
14
|
+
include Lutaml::Model::Schema::SharedMethods
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
def generate(
|
17
|
+
klass,
|
18
|
+
id: nil,
|
19
|
+
title: nil,
|
20
|
+
description: nil,
|
21
|
+
pretty: false
|
22
|
+
)
|
23
|
+
options = {
|
24
|
+
schema: "https://json-schema.org/draft/2020-12/schema",
|
25
|
+
id: id,
|
26
|
+
title: title,
|
27
|
+
description: description,
|
28
|
+
pretty: pretty,
|
29
|
+
}
|
18
30
|
|
19
|
-
|
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
|
31
|
+
super(klass, options)
|
25
32
|
end
|
26
|
-
defs
|
27
|
-
end
|
28
33
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
34
|
+
def format_schema(schema, options)
|
35
|
+
options[:pretty] ? JSON.pretty_generate(schema) : schema.to_json
|
36
|
+
end
|
37
|
+
|
38
|
+
def generate_model_classes(schema)
|
39
|
+
definitions = definition_collection_class.new(schema["$defs"])
|
36
40
|
|
37
|
-
|
38
|
-
|
39
|
-
|
41
|
+
definitions.transform_values do |definition|
|
42
|
+
generate_model_class(definition)
|
43
|
+
end
|
40
44
|
end
|
41
|
-
end
|
42
45
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
"items" => { "type" => get_json_type(attr.type) },
|
50
|
-
}
|
51
|
-
else
|
52
|
-
{ "type" => get_json_type(attr.type) }
|
46
|
+
private
|
47
|
+
|
48
|
+
def generate_model_class(schema)
|
49
|
+
template = File.join(__dir__, "templates", "model.erb")
|
50
|
+
|
51
|
+
Lutaml::Model::Schema::Renderer.render(template, schema: schema)
|
53
52
|
end
|
54
|
-
end
|
55
53
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
Lutaml::Model::Type::Integer => "integer",
|
60
|
-
Lutaml::Model::Type::Boolean => "boolean",
|
61
|
-
Lutaml::Model::Type::Float => "number",
|
62
|
-
Lutaml::Model::Type::Hash => "object",
|
63
|
-
}[type] || "string" # Default to string for unknown types
|
54
|
+
def definition_collection_class
|
55
|
+
Lutaml::Model::Schema::Decorators::DefinitionCollection
|
56
|
+
end
|
64
57
|
end
|
65
58
|
end
|
66
59
|
end
|