lutaml-model 0.7.1 → 0.7.7
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/dependent-tests.yml +4 -0
- data/.github/workflows/rake.yml +12 -0
- data/.github/workflows/release.yml +3 -0
- data/.gitignore +6 -1
- data/.irbrc +1 -0
- data/.pryrc +1 -0
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +58 -84
- data/Gemfile +4 -1
- data/README.adoc +3115 -365
- data/RELEASE_NOTES.adoc +346 -0
- data/docs/custom_adapters.adoc +144 -0
- 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 +242 -100
- data/lib/lutaml/model/choice.rb +30 -0
- data/lib/lutaml/model/collection.rb +212 -0
- data/lib/lutaml/model/comparable_model.rb +3 -3
- data/lib/lutaml/model/config.rb +71 -42
- 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/polymorphic_error.rb +7 -2
- 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 +44 -0
- data/lib/lutaml/model/global_register.rb +41 -0
- data/lib/lutaml/model/hash_adapter/document.rb +11 -0
- data/lib/lutaml/model/hash_adapter/mapping.rb +19 -0
- data/lib/lutaml/model/hash_adapter/mapping_rule.rb +9 -0
- data/lib/lutaml/model/hash_adapter/standard_adapter.rb +17 -0
- data/lib/lutaml/model/hash_adapter/transform.rb +8 -0
- data/lib/lutaml/model/hash_adapter.rb +21 -0
- data/lib/lutaml/model/json/document.rb +11 -0
- data/lib/lutaml/model/json/mapping.rb +19 -0
- data/lib/lutaml/model/json/mapping_rule.rb +9 -0
- data/lib/lutaml/model/{json_adapter → json}/multi_json_adapter.rb +4 -5
- data/lib/lutaml/model/{json_adapter/standard_json_adapter.rb → json/standard_adapter.rb} +5 -3
- data/lib/lutaml/model/json/transform.rb +8 -0
- data/lib/lutaml/model/json.rb +21 -0
- 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 +28 -0
- data/lib/lutaml/model/mapping/key_value_mapping.rb +85 -13
- data/lib/lutaml/model/mapping/key_value_mapping_rule.rb +5 -0
- data/lib/lutaml/model/mapping/mapping.rb +13 -0
- data/lib/lutaml/model/mapping/mapping_rule.rb +15 -9
- data/lib/lutaml/model/register.rb +105 -0
- data/lib/lutaml/model/{toml_adapter.rb → registrable.rb} +1 -1
- 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/serialization_adapter.rb +22 -0
- data/lib/lutaml/model/serialize.rb +296 -548
- data/lib/lutaml/model/services/base.rb +11 -0
- data/lib/lutaml/model/services/logger.rb +54 -0
- data/lib/lutaml/model/services/rule_value_extractor.rb +92 -0
- data/lib/lutaml/model/services/transformer.rb +48 -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 +5 -0
- data/lib/lutaml/model/toml/document.rb +11 -0
- data/lib/lutaml/model/toml/mapping.rb +27 -0
- data/lib/lutaml/model/toml/mapping_rule.rb +9 -0
- data/lib/lutaml/model/{toml_adapter → toml}/toml_rb_adapter.rb +3 -3
- data/lib/lutaml/model/toml/tomlib_adapter.rb +19 -0
- data/lib/lutaml/model/toml/transform.rb +8 -0
- data/lib/lutaml/model/toml.rb +30 -0
- data/lib/lutaml/model/transform/key_value_transform.rb +297 -0
- data/lib/lutaml/model/transform/xml_transform.rb +228 -0
- data/lib/lutaml/model/transform.rb +93 -0
- 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 +11 -10
- data/lib/lutaml/model/type.rb +5 -2
- data/lib/lutaml/model/uninitialized_class.rb +1 -1
- data/lib/lutaml/model/utils.rb +56 -4
- data/lib/lutaml/model/validation.rb +6 -4
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/{xml_adapter → xml}/builder/nokogiri.rb +2 -2
- data/lib/lutaml/model/{xml_adapter → xml}/builder/oga.rb +10 -10
- data/lib/lutaml/model/{xml_adapter → xml}/builder/ox.rb +1 -1
- data/lib/lutaml/model/{xml_adapter/xml_document.rb → xml/document.rb} +42 -25
- data/lib/lutaml/model/xml/element.rb +32 -0
- data/lib/lutaml/model/xml/mapping.rb +471 -0
- data/lib/lutaml/model/xml/mapping_rule.rb +149 -0
- data/lib/lutaml/model/xml/nokogiri_adapter.rb +234 -0
- data/lib/lutaml/model/{xml_adapter → xml}/oga/document.rb +1 -1
- data/lib/lutaml/model/{xml_adapter → xml}/oga/element.rb +7 -2
- data/lib/lutaml/model/xml/oga_adapter.rb +172 -0
- data/lib/lutaml/model/xml/ox_adapter.rb +229 -0
- data/lib/lutaml/model/xml/transform.rb +8 -0
- data/lib/lutaml/model/{xml_adapter → xml}/xml_attribute.rb +1 -1
- data/lib/lutaml/model/{xml_adapter → xml}/xml_element.rb +9 -31
- data/lib/lutaml/model/{xml_adapter → xml}/xml_namespace.rb +1 -1
- data/lib/lutaml/model/xml.rb +31 -0
- data/lib/lutaml/model/xml_adapter/element.rb +11 -25
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +6 -223
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +13 -163
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +10 -207
- data/lib/lutaml/model/yaml/document.rb +10 -0
- data/lib/lutaml/model/yaml/mapping.rb +19 -0
- data/lib/lutaml/model/yaml/mapping_rule.rb +9 -0
- data/lib/lutaml/model/{yaml_adapter/standard_yaml_adapter.rb → yaml/standard_adapter.rb} +4 -3
- data/lib/lutaml/model/yaml/transform.rb +8 -0
- data/lib/lutaml/model/yaml.rb +21 -0
- 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 +15 -4
- data/lutaml-model.gemspec +0 -4
- data/spec/benchmarks/xml_parsing_benchmark_spec.rb +5 -6
- 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/cdata_spec.rb +7 -7
- data/spec/lutaml/model/choice_spec.rb +44 -0
- data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +598 -0
- data/spec/lutaml/model/custom_collection_spec.rb +830 -0
- data/spec/lutaml/model/custom_vobject_adapter_spec.rb +1226 -0
- data/spec/lutaml/model/global_register_spec.rb +108 -0
- data/spec/lutaml/model/group_spec.rb +27 -10
- data/spec/lutaml/model/hash/adapter_spec.rb +255 -0
- data/spec/lutaml/model/json_adapter_spec.rb +6 -6
- data/spec/lutaml/model/jsonl/standard_adapter_spec.rb +91 -0
- data/spec/lutaml/model/jsonl_spec.rb +229 -0
- data/spec/lutaml/model/key_value_mapping_spec.rb +25 -1
- data/spec/lutaml/model/mixed_content_spec.rb +24 -24
- data/spec/lutaml/model/multiple_mapping_spec.rb +6 -6
- data/spec/lutaml/model/ordered_content_spec.rb +6 -6
- data/spec/lutaml/model/polymorphic_spec.rb +178 -0
- data/spec/lutaml/model/register/key_value_spec.rb +275 -0
- data/spec/lutaml/model/register/xml_spec.rb +185 -0
- data/spec/lutaml/model/register_spec.rb +147 -0
- data/spec/lutaml/model/root_mappings_spec.rb +3 -3
- 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 +201 -94
- data/spec/lutaml/model/toml_adapter_spec.rb +6 -6
- data/spec/lutaml/model/toml_spec.rb +51 -0
- data/spec/lutaml/model/transformation_spec.rb +72 -15
- data/spec/lutaml/model/type_spec.rb +8 -4
- data/spec/lutaml/model/uninitialized_class_spec.rb +96 -0
- 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/namespace_spec.rb +57 -0
- data/spec/lutaml/model/xml/root_mappings/nested_child_mappings_spec.rb +217 -0
- data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
- data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +2 -2
- data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +2 -2
- data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +2 -2
- data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +12 -12
- data/spec/lutaml/model/xml_adapter_spec.rb +30 -6
- data/spec/lutaml/model/xml_mapping_rule_spec.rb +14 -7
- data/spec/lutaml/model/xml_mapping_spec.rb +27 -15
- data/spec/lutaml/model/xml_spec.rb +63 -0
- data/spec/lutaml/model/yaml_adapter_spec.rb +3 -5
- 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 +4 -3
- metadata +162 -60
- data/lib/lutaml/model/json_adapter/json_document.rb +0 -20
- data/lib/lutaml/model/json_adapter/json_object.rb +0 -28
- data/lib/lutaml/model/loggable.rb +0 -15
- data/lib/lutaml/model/mapping/json_mapping.rb +0 -17
- data/lib/lutaml/model/mapping/toml_mapping.rb +0 -25
- data/lib/lutaml/model/mapping/xml_mapping.rb +0 -389
- data/lib/lutaml/model/mapping/xml_mapping_rule.rb +0 -139
- data/lib/lutaml/model/mapping/yaml_mapping.rb +0 -17
- data/lib/lutaml/model/mapping.rb +0 -14
- data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
- data/lib/lutaml/model/toml_adapter/toml_document.rb +0 -20
- data/lib/lutaml/model/toml_adapter/toml_object.rb +0 -28
- data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +0 -20
- data/lib/lutaml/model/yaml_adapter/yaml_document.rb +0 -20
- data/lib/lutaml/model/yaml_adapter/yaml_object.rb +0 -28
- data/lib/lutaml/model/yaml_adapter.rb +0 -8
|
@@ -3,6 +3,8 @@ module Lutaml
|
|
|
3
3
|
class MappingRule
|
|
4
4
|
attr_reader :name,
|
|
5
5
|
:to,
|
|
6
|
+
:to_instance,
|
|
7
|
+
:as_attribute,
|
|
6
8
|
:render_nil,
|
|
7
9
|
:render_default,
|
|
8
10
|
:render_empty,
|
|
@@ -44,6 +46,8 @@ module Lutaml
|
|
|
44
46
|
def initialize(
|
|
45
47
|
name,
|
|
46
48
|
to:,
|
|
49
|
+
to_instance: nil,
|
|
50
|
+
as_attribute: nil,
|
|
47
51
|
render_nil: false,
|
|
48
52
|
render_default: false,
|
|
49
53
|
render_empty: false,
|
|
@@ -61,6 +65,8 @@ module Lutaml
|
|
|
61
65
|
)
|
|
62
66
|
@name = name
|
|
63
67
|
@to = to
|
|
68
|
+
@to_instance = to_instance
|
|
69
|
+
@as_attribute = as_attribute
|
|
64
70
|
@render_nil = render_nil
|
|
65
71
|
@render_default = render_default
|
|
66
72
|
@render_empty = render_empty
|
|
@@ -212,12 +218,15 @@ module Lutaml
|
|
|
212
218
|
def deserialize(model, value, attributes, mapper_class = nil)
|
|
213
219
|
handle_custom_method(model, value, mapper_class) ||
|
|
214
220
|
handle_delegate(model, value, attributes) ||
|
|
215
|
-
handle_transform_method(model, value, attributes)
|
|
216
|
-
assign_value(model, value)
|
|
221
|
+
handle_transform_method(model, value, attributes)
|
|
217
222
|
end
|
|
218
223
|
|
|
219
|
-
def
|
|
220
|
-
!custom_methods.empty?
|
|
224
|
+
def has_custom_method_for_serialization?
|
|
225
|
+
!custom_methods.empty? && custom_methods[:to]
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def has_custom_method_for_deserialization?
|
|
229
|
+
!custom_methods.empty? && custom_methods[:from]
|
|
221
230
|
end
|
|
222
231
|
|
|
223
232
|
def multiple_mappings?
|
|
@@ -280,14 +289,11 @@ module Lutaml
|
|
|
280
289
|
delegate_value = model.public_send(delegate)
|
|
281
290
|
return if Utils.initialized?(delegate_value) && !delegate_value.nil?
|
|
282
291
|
|
|
283
|
-
model.public_send(:"#{delegate}=", attributes[delegate].type.new)
|
|
292
|
+
model.public_send(:"#{delegate}=", attributes[delegate].type(model.__register).new)
|
|
284
293
|
end
|
|
285
294
|
|
|
286
295
|
def handle_transform_method(model, value, attributes)
|
|
287
|
-
|
|
288
|
-
return unless transform_method
|
|
289
|
-
|
|
290
|
-
assign_value(model, transform_method.call(value))
|
|
296
|
+
assign_value(model, ImportTransformer.call(value, self, attributes[to]))
|
|
291
297
|
true
|
|
292
298
|
end
|
|
293
299
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
class Register
|
|
6
|
+
attr_reader :id, :models
|
|
7
|
+
|
|
8
|
+
def initialize(id)
|
|
9
|
+
@id = id
|
|
10
|
+
@models = {}
|
|
11
|
+
@global_substitutions = {}
|
|
12
|
+
@type_class_cache = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def register_model(klass, id: nil)
|
|
16
|
+
id ||= Utils.base_class_snake_case(klass).to_sym
|
|
17
|
+
return Lutaml::Model::Type.register(id, klass) if klass <= Lutaml::Model::Type::Value
|
|
18
|
+
raise NotRegistrableClassError.new(klass) unless klass.include?(Lutaml::Model::Registrable)
|
|
19
|
+
|
|
20
|
+
@models[id.to_sym] = klass
|
|
21
|
+
@models[klass.to_s] = klass
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def resolve(klass_str)
|
|
25
|
+
@models[klass_str.to_s]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def get_class(klass_name)
|
|
29
|
+
expected_class = get_class_without_register(klass_name)
|
|
30
|
+
if !(expected_class < Lutaml::Model::Type::Value)
|
|
31
|
+
expected_class.class_variable_set(:@@__register, id)
|
|
32
|
+
end
|
|
33
|
+
expected_class
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def register_model_tree(klass)
|
|
37
|
+
register_model(klass)
|
|
38
|
+
if klass.include?(Lutaml::Model::Serialize)
|
|
39
|
+
register_attributes(klass.attributes)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def register_global_type_substitution(from_type:, to_type:)
|
|
44
|
+
@global_substitutions[from_type] = to_type
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def register_attributes(attributes)
|
|
48
|
+
attributes.each_value do |attribute|
|
|
49
|
+
next unless attribute.unresolved_type.is_a?(Class)
|
|
50
|
+
next if built_in_type?(attribute.unresolved_type) || attribute.unresolved_type.nil?
|
|
51
|
+
|
|
52
|
+
register_model_tree(attribute.unresolved_type)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def substitutable?(klass)
|
|
57
|
+
@global_substitutions.key?(klass)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def substitute(klass)
|
|
61
|
+
@global_substitutions[klass]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def get_class_without_register(klass_name)
|
|
65
|
+
klass = extract_class_from(klass_name)
|
|
66
|
+
raise Lutaml::Model::UnknownTypeError.new(klass_name) unless klass
|
|
67
|
+
|
|
68
|
+
substitute(klass) || substitute(klass_name) || klass
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def get_type_class(klass_name)
|
|
74
|
+
if klass_name.is_a?(String)
|
|
75
|
+
Lutaml::Model::Type.const_get(klass_name)
|
|
76
|
+
elsif klass_name.is_a?(Symbol)
|
|
77
|
+
Lutaml::Model::Type.lookup(klass_name)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def built_in_type?(type)
|
|
82
|
+
Lutaml::Model::Type::TYPE_CODES.value?(type.inspect) ||
|
|
83
|
+
Lutaml::Model::Type::TYPE_CODES.key?(type.to_s.to_sym)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def extract_class_from(klass)
|
|
87
|
+
return klass if klass.is_a?(Class)
|
|
88
|
+
return @models[klass] if @models.key?(klass)
|
|
89
|
+
|
|
90
|
+
# Check cache first
|
|
91
|
+
return @type_class_cache[klass] if @type_class_cache.key?(klass)
|
|
92
|
+
|
|
93
|
+
# If not in cache, try to get type class and cache it
|
|
94
|
+
if type_klass = get_type_class(klass)
|
|
95
|
+
@type_class_cache[klass] = type_klass
|
|
96
|
+
type_klass
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def clear_type_class_cache
|
|
101
|
+
@type_class_cache.clear
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require_relative "generator/definitions_collection"
|
|
2
|
+
# require_relative "generator/collection"
|
|
3
|
+
require_relative "generator/ref"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Model
|
|
7
|
+
module Schema
|
|
8
|
+
class BaseSchema
|
|
9
|
+
class << self
|
|
10
|
+
def generate(klass, options = {})
|
|
11
|
+
schema = new(
|
|
12
|
+
klass,
|
|
13
|
+
schema: options[:schema],
|
|
14
|
+
id: options[:id],
|
|
15
|
+
title: options[:title],
|
|
16
|
+
description: options[:description],
|
|
17
|
+
).generate_schema_hash
|
|
18
|
+
|
|
19
|
+
format_schema(schema, options)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
attr_reader :schema, :klass
|
|
24
|
+
attr_accessor :id, :title, :description
|
|
25
|
+
|
|
26
|
+
def initialize(klass, schema:, id: nil, title: nil, description: nil)
|
|
27
|
+
@schema = schema
|
|
28
|
+
@klass = klass
|
|
29
|
+
@id = id
|
|
30
|
+
@title = title
|
|
31
|
+
@description = description
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def generate_schema_hash
|
|
35
|
+
{
|
|
36
|
+
"$schema" => schema,
|
|
37
|
+
"$id" => id,
|
|
38
|
+
"description" => description,
|
|
39
|
+
"$ref" => "#/$defs/#{klass.name.gsub('::', '_')}",
|
|
40
|
+
"$defs" => generate_definitions(klass),
|
|
41
|
+
}.compact
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def polymorphic?(attr)
|
|
47
|
+
Utils.present?(attr.options[:polymorphic])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def generate_definitions(klass)
|
|
51
|
+
Generator::DefinitionsCollection.from_class(klass).to_schema
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def generate_polymorphic_definitions(attr)
|
|
55
|
+
return {} unless polymorphic?(attr)
|
|
56
|
+
|
|
57
|
+
attr.options[:polymorphic].each_with_object({}) do |child, result|
|
|
58
|
+
result.merge!(generate_definitions(child))
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Decorators
|
|
7
|
+
class Attribute
|
|
8
|
+
ATTRIBUTE_TYPES = {
|
|
9
|
+
"string" => ":string",
|
|
10
|
+
"integer" => ":integer",
|
|
11
|
+
"boolean" => ":boolean",
|
|
12
|
+
"number" => ":float",
|
|
13
|
+
"object" => ":hash",
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
attr_reader :name, :options, :polymorphic
|
|
17
|
+
attr_accessor :type, :base_class
|
|
18
|
+
|
|
19
|
+
# @param attribute [Hash] The JSON schema attribute to be decorated.
|
|
20
|
+
def initialize(name, options)
|
|
21
|
+
@name = name
|
|
22
|
+
@options = options
|
|
23
|
+
@type = extract_type(options)
|
|
24
|
+
@polymorphic = options["oneOf"]&.map { |choice| choice["$ref"].split("/").last }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def default
|
|
28
|
+
@options["default"]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def polymorphic?
|
|
32
|
+
!!@polymorphic
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def polymorphic_classes
|
|
36
|
+
return [] unless polymorphic?
|
|
37
|
+
|
|
38
|
+
@base_class.sub_classes.map do |klass|
|
|
39
|
+
klass.namespaced_name.gsub("_", "::")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def collection?
|
|
44
|
+
@options["type"] == "array"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def collection
|
|
48
|
+
return unless collection?
|
|
49
|
+
|
|
50
|
+
if @options["minItems"] && @options["maxItems"]
|
|
51
|
+
@options["minItems"]..@options["maxItems"]
|
|
52
|
+
elsif @options["minItems"]
|
|
53
|
+
@options["minItems"]..Float::INFINITY
|
|
54
|
+
elsif @options["maxItems"]
|
|
55
|
+
0..@options["maxItems"]
|
|
56
|
+
else
|
|
57
|
+
true
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def choice?
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def lutaml_type_symbol(type)
|
|
68
|
+
return ":string" if type.nil?
|
|
69
|
+
|
|
70
|
+
type = type.first if type.is_a?(Array)
|
|
71
|
+
|
|
72
|
+
return array_type(options) if type == "array"
|
|
73
|
+
return object_type(options) if type == "object"
|
|
74
|
+
|
|
75
|
+
ATTRIBUTE_TYPES[type] || ":string"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def array_type(options)
|
|
79
|
+
lutaml_type_symbol(options["items"]["type"])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def extract_type(options)
|
|
83
|
+
return extract_type(options["items"]) if options["type"] == "array"
|
|
84
|
+
|
|
85
|
+
if options["type"]
|
|
86
|
+
lutaml_type_symbol(options["type"])
|
|
87
|
+
elsif options["$ref"]
|
|
88
|
+
options["$ref"].split("/").last.gsub("_", "::")
|
|
89
|
+
else
|
|
90
|
+
":string" # Default to string if type is not recognized
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def object_type(options)
|
|
95
|
+
if options["oneOf"]
|
|
96
|
+
{
|
|
97
|
+
"polymorphic" => options["oneOf"].map do |choice|
|
|
98
|
+
choice["$ref"].split("/").last.gsub("_", "::")
|
|
99
|
+
end,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
elsif options["properties"]
|
|
103
|
+
options["properties"].map do |name, attr|
|
|
104
|
+
Attribute.new(name, attr)
|
|
105
|
+
end
|
|
106
|
+
else
|
|
107
|
+
":hash" # Default to hash if no specific type is defined
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "attribute"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Model
|
|
7
|
+
module Schema
|
|
8
|
+
module Decorators
|
|
9
|
+
class Choices
|
|
10
|
+
attr_reader :attributes
|
|
11
|
+
|
|
12
|
+
# Decorates a collection of choice attributes.
|
|
13
|
+
# This class is used to handle attributes that are part of a choice
|
|
14
|
+
# constraint in a JSON schema. It provides a way to access the choice
|
|
15
|
+
# attributes in a structured manner.
|
|
16
|
+
def initialize(attributes)
|
|
17
|
+
@attributes = attributes.values
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def choice?
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def polymorphic?
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -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
|