lutaml-jsonschema 0.1.0
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 +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/README.md +39 -0
- data/Rakefile +26 -0
- data/exe/lutaml-jsonschema +6 -0
- data/frontend/index.html +60 -0
- data/frontend/package-lock.json +2715 -0
- data/frontend/package.json +27 -0
- data/frontend/public/lutaml-logo-dark.svg +1 -0
- data/frontend/public/lutaml-logo-full-dark.svg +1 -0
- data/frontend/public/lutaml-logo-full-light.svg +1 -0
- data/frontend/public/lutaml-logo-light.svg +1 -0
- data/frontend/src/App.vue +80 -0
- data/frontend/src/__tests__/useBuilderField.test.ts +137 -0
- data/frontend/src/__tests__/useDefinitionResolver.test.ts +46 -0
- data/frontend/src/__tests__/useSchemaTypes.test.ts +219 -0
- data/frontend/src/app.ts +10 -0
- data/frontend/src/components/AppHeader.vue +152 -0
- data/frontend/src/components/AppSidebar.vue +427 -0
- data/frontend/src/components/DetailPanel.vue +403 -0
- data/frontend/src/components/SchemaBuilder.vue +543 -0
- data/frontend/src/components/SchemaStructure.vue +168 -0
- data/frontend/src/components/SearchModal.vue +275 -0
- data/frontend/src/composables/useBuilderField.ts +92 -0
- data/frontend/src/composables/useDefinitionResolver.ts +17 -0
- data/frontend/src/composables/useSchemaTypes.ts +152 -0
- data/frontend/src/composables/useSearch.ts +104 -0
- data/frontend/src/router.ts +14 -0
- data/frontend/src/stores/schemaStore.ts +118 -0
- data/frontend/src/stores/uiStore.ts +78 -0
- data/frontend/src/style.css +194 -0
- data/frontend/src/types.ts +70 -0
- data/frontend/src/views/HomeView.vue +396 -0
- data/frontend/tsconfig.json +20 -0
- data/frontend/vite.config.ts +28 -0
- data/lib/lutaml/jsonschema/base.rb +11 -0
- data/lib/lutaml/jsonschema/cli.rb +102 -0
- data/lib/lutaml/jsonschema/combiner.rb +54 -0
- data/lib/lutaml/jsonschema/configuration.rb +47 -0
- data/lib/lutaml/jsonschema/link.rb +25 -0
- data/lib/lutaml/jsonschema/property_entry.rb +15 -0
- data/lib/lutaml/jsonschema/reference_resolver.rb +74 -0
- data/lib/lutaml/jsonschema/schema.rb +205 -0
- data/lib/lutaml/jsonschema/schema_set.rb +217 -0
- data/lib/lutaml/jsonschema/spa/generator.rb +22 -0
- data/lib/lutaml/jsonschema/spa/metadata.rb +23 -0
- data/lib/lutaml/jsonschema/spa/output_strategy.rb +17 -0
- data/lib/lutaml/jsonschema/spa/spa_builder.rb +178 -0
- data/lib/lutaml/jsonschema/spa/spa_definition.rb +27 -0
- data/lib/lutaml/jsonschema/spa/spa_document.rb +23 -0
- data/lib/lutaml/jsonschema/spa/spa_property.rb +47 -0
- data/lib/lutaml/jsonschema/spa/spa_schema.rb +29 -0
- data/lib/lutaml/jsonschema/spa/spa_search_entry.rb +21 -0
- data/lib/lutaml/jsonschema/spa/vue_inlined_strategy.rb +53 -0
- data/lib/lutaml/jsonschema/version.rb +7 -0
- data/lib/lutaml/jsonschema.rb +29 -0
- data/sig/lutaml/jsonschema.rbs +6 -0
- metadata +163 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Jsonschema
|
|
5
|
+
class ReferenceResolver
|
|
6
|
+
def initialize(schemas = {})
|
|
7
|
+
@schemas = schemas
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def resolve(ref, root_schema = nil)
|
|
11
|
+
return nil unless ref
|
|
12
|
+
|
|
13
|
+
if ref.start_with?("#/")
|
|
14
|
+
resolve_local(ref, root_schema)
|
|
15
|
+
else
|
|
16
|
+
resolve_remote(ref)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def resolve_local(ref, schema)
|
|
21
|
+
parts = ref.delete_prefix("#/").split("/")
|
|
22
|
+
current = schema
|
|
23
|
+
|
|
24
|
+
parts.each do |part|
|
|
25
|
+
case current
|
|
26
|
+
when Schema
|
|
27
|
+
current = navigate_schema(current, part)
|
|
28
|
+
when Array
|
|
29
|
+
entry = current.find { |e| e.is_a?(PropertyEntry) && e.name == part }
|
|
30
|
+
current = entry&.schema
|
|
31
|
+
else
|
|
32
|
+
return nil
|
|
33
|
+
end
|
|
34
|
+
return nil unless current
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
current.is_a?(Schema) && current.dollar_ref ? resolve(current.dollar_ref, schema) : current
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def resolve_remote(ref)
|
|
41
|
+
return nil unless @schemas
|
|
42
|
+
|
|
43
|
+
if ref.include?("#")
|
|
44
|
+
file_path, fragment = ref.split("#", 2)
|
|
45
|
+
schema = @schemas[file_path]
|
|
46
|
+
schema ? resolve_local("##{fragment}", schema) : nil
|
|
47
|
+
else
|
|
48
|
+
@schemas[ref]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def navigate_schema(schema, part)
|
|
55
|
+
case part
|
|
56
|
+
when "definitions", "$defs"
|
|
57
|
+
schema.definition_entries
|
|
58
|
+
when "properties"
|
|
59
|
+
schema.property_entries
|
|
60
|
+
when "patternProperties"
|
|
61
|
+
schema.pattern_property_entries
|
|
62
|
+
when "items"
|
|
63
|
+
schema.items
|
|
64
|
+
when "allOf" then schema.all_of
|
|
65
|
+
when "anyOf" then schema.any_of
|
|
66
|
+
when "oneOf" then schema.one_of
|
|
67
|
+
else
|
|
68
|
+
entries = schema.definition_entries || []
|
|
69
|
+
entries.find { |e| e.name == part }&.schema
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Jsonschema
|
|
5
|
+
class Schema < Base
|
|
6
|
+
# Document-level keywords
|
|
7
|
+
attribute :dollar_schema, :string
|
|
8
|
+
attribute :dollar_id, :string
|
|
9
|
+
attribute :dollar_ref, :string
|
|
10
|
+
attribute :dollar_comment, :string
|
|
11
|
+
attribute :dollar_anchor, :string
|
|
12
|
+
|
|
13
|
+
# Metadata
|
|
14
|
+
attribute :title, :string
|
|
15
|
+
attribute :description, :string
|
|
16
|
+
attribute :default, :string
|
|
17
|
+
attribute :examples, :string, collection: true
|
|
18
|
+
attribute :deprecated, :boolean
|
|
19
|
+
attribute :read_only, :boolean
|
|
20
|
+
attribute :write_only, :boolean
|
|
21
|
+
|
|
22
|
+
# Type keywords
|
|
23
|
+
attribute :type, :string
|
|
24
|
+
attribute :format, :string
|
|
25
|
+
|
|
26
|
+
# Object keywords
|
|
27
|
+
attribute :property_entries, PropertyEntry, collection: true, initialize_empty: true
|
|
28
|
+
attribute :pattern_property_entries, PropertyEntry, collection: true, initialize_empty: true
|
|
29
|
+
attribute :required, :string, collection: true, initialize_empty: true
|
|
30
|
+
attribute :additional_properties, :boolean
|
|
31
|
+
attribute :additional_properties_schema, Schema
|
|
32
|
+
attribute :min_properties, :integer
|
|
33
|
+
attribute :max_properties, :integer
|
|
34
|
+
|
|
35
|
+
# Array keywords
|
|
36
|
+
attribute :items, Schema
|
|
37
|
+
attribute :min_items, :integer
|
|
38
|
+
attribute :max_items, :integer
|
|
39
|
+
attribute :unique_items, :boolean
|
|
40
|
+
attribute :contains, Schema
|
|
41
|
+
|
|
42
|
+
# Numeric keywords
|
|
43
|
+
attribute :minimum, :float
|
|
44
|
+
attribute :maximum, :float
|
|
45
|
+
attribute :exclusive_minimum, :float
|
|
46
|
+
attribute :exclusive_maximum, :float
|
|
47
|
+
attribute :multiple_of, :float
|
|
48
|
+
|
|
49
|
+
# String keywords
|
|
50
|
+
attribute :min_length, :integer
|
|
51
|
+
attribute :max_length, :integer
|
|
52
|
+
attribute :pattern, :string
|
|
53
|
+
attribute :content_type, :string
|
|
54
|
+
attribute :content_encoding, :string
|
|
55
|
+
|
|
56
|
+
# Enum / const
|
|
57
|
+
attribute :enum, :string, collection: true
|
|
58
|
+
attribute :const, :string
|
|
59
|
+
|
|
60
|
+
# Composition (recursive)
|
|
61
|
+
attribute :all_of, Schema, collection: true, initialize_empty: true
|
|
62
|
+
attribute :any_of, Schema, collection: true, initialize_empty: true
|
|
63
|
+
attribute :one_of, Schema, collection: true, initialize_empty: true
|
|
64
|
+
attribute :not_schema, Schema
|
|
65
|
+
|
|
66
|
+
# Conditional
|
|
67
|
+
attribute :if_schema, Schema
|
|
68
|
+
attribute :then_schema, Schema
|
|
69
|
+
attribute :else_schema, Schema
|
|
70
|
+
|
|
71
|
+
# Definitions ($defs / definitions)
|
|
72
|
+
attribute :definition_entries, PropertyEntry, collection: true, initialize_empty: true
|
|
73
|
+
|
|
74
|
+
# Hyper-schema links
|
|
75
|
+
attribute :links, Link, collection: true, initialize_empty: true
|
|
76
|
+
|
|
77
|
+
json do
|
|
78
|
+
map "$schema", to: :dollar_schema
|
|
79
|
+
map ["$id", "id"], to: :dollar_id
|
|
80
|
+
map "$ref", to: :dollar_ref
|
|
81
|
+
map "$comment", to: :dollar_comment
|
|
82
|
+
map "$anchor", to: :dollar_anchor
|
|
83
|
+
map "title", to: :title
|
|
84
|
+
map "description", to: :description
|
|
85
|
+
map "default", to: :default
|
|
86
|
+
map ["examples", "example"], to: :examples
|
|
87
|
+
map "deprecated", to: :deprecated
|
|
88
|
+
map "readOnly", to: :read_only
|
|
89
|
+
map "writeOnly", to: :write_only
|
|
90
|
+
map "type", to: :type,
|
|
91
|
+
with: { from: :parse_type, to: :serialize_type }
|
|
92
|
+
map "format", to: :format
|
|
93
|
+
map "properties", to: :property_entries,
|
|
94
|
+
child_mappings: { name: :key, schema: :value }
|
|
95
|
+
map "patternProperties", to: :pattern_property_entries,
|
|
96
|
+
child_mappings: { name: :key, schema: :value }
|
|
97
|
+
map "required", to: :required
|
|
98
|
+
map "additionalProperties", to: :additional_properties,
|
|
99
|
+
with: { from: :parse_additional_properties,
|
|
100
|
+
to: :serialize_additional_properties }
|
|
101
|
+
map "minProperties", to: :min_properties
|
|
102
|
+
map "maxProperties", to: :max_properties
|
|
103
|
+
map "items", to: :items
|
|
104
|
+
map "minItems", to: :min_items
|
|
105
|
+
map "maxItems", to: :max_items
|
|
106
|
+
map "uniqueItems", to: :unique_items
|
|
107
|
+
map "contains", to: :contains
|
|
108
|
+
map "minimum", to: :minimum
|
|
109
|
+
map "maximum", to: :maximum
|
|
110
|
+
map "exclusiveMinimum", to: :exclusive_minimum
|
|
111
|
+
map "exclusiveMaximum", to: :exclusive_maximum
|
|
112
|
+
map "multipleOf", to: :multiple_of
|
|
113
|
+
map "minLength", to: :min_length
|
|
114
|
+
map "maxLength", to: :max_length
|
|
115
|
+
map "pattern", to: :pattern
|
|
116
|
+
map "contentMediaType", to: :content_type
|
|
117
|
+
map "contentEncoding", to: :content_encoding
|
|
118
|
+
map "enum", to: :enum
|
|
119
|
+
map "const", to: :const
|
|
120
|
+
map "allOf", to: :all_of
|
|
121
|
+
map "anyOf", to: :any_of
|
|
122
|
+
map "oneOf", to: :one_of
|
|
123
|
+
map "not", to: :not_schema
|
|
124
|
+
map "if", to: :if_schema
|
|
125
|
+
map "then", to: :then_schema
|
|
126
|
+
map "else", to: :else_schema
|
|
127
|
+
map "$defs", to: :definition_entries,
|
|
128
|
+
child_mappings: { name: :key, schema: :value }
|
|
129
|
+
map "definitions", to: :definition_entries,
|
|
130
|
+
with: { from: :parse_legacy_definitions, to: :noop_serializer }
|
|
131
|
+
map "links", to: :links
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def parse_type(instance, value)
|
|
135
|
+
instance.type = value.is_a?(Array) ? value.join(",") : value
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def serialize_type(instance, hash)
|
|
139
|
+
t = instance.type
|
|
140
|
+
return unless t
|
|
141
|
+
|
|
142
|
+
parts = t.split(",")
|
|
143
|
+
hash["type"] = parts.length == 1 ? parts.first : parts
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def parse_additional_properties(instance, value)
|
|
147
|
+
case value
|
|
148
|
+
when true, false
|
|
149
|
+
instance.additional_properties = value
|
|
150
|
+
when Hash
|
|
151
|
+
instance.additional_properties_schema = Schema.from_json(value.to_json)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def serialize_additional_properties(instance, hash)
|
|
156
|
+
if instance.additional_properties_schema
|
|
157
|
+
hash["additionalProperties"] = JSON.parse(instance.additional_properties_schema.to_json)
|
|
158
|
+
elsif !instance.additional_properties.nil?
|
|
159
|
+
hash["additionalProperties"] = instance.additional_properties
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def object?
|
|
164
|
+
types.include?("object")
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def array?
|
|
168
|
+
types.include?("array")
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def types
|
|
172
|
+
return [] unless type
|
|
173
|
+
|
|
174
|
+
type.split(",")
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def ref?
|
|
178
|
+
!!dollar_ref
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def all_property_entries
|
|
182
|
+
result = property_entries.dup
|
|
183
|
+
all_of.each { |s| result.concat(s.all_property_entries) }
|
|
184
|
+
result
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def noop_serializer(_instance, _hash); end
|
|
188
|
+
|
|
189
|
+
def parse_legacy_definitions(instance, value)
|
|
190
|
+
return unless value.is_a?(Hash)
|
|
191
|
+
|
|
192
|
+
value.each do |name, schema_hash|
|
|
193
|
+
next if instance.definition_entries.any? { |e| e.name == name }
|
|
194
|
+
|
|
195
|
+
instance.definition_entries.push(
|
|
196
|
+
PropertyEntry.new(
|
|
197
|
+
name: name,
|
|
198
|
+
schema: Schema.from_json(schema_hash.to_json)
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Jsonschema
|
|
5
|
+
class SchemaSet
|
|
6
|
+
attr_reader :schemas, :base_dir
|
|
7
|
+
|
|
8
|
+
def initialize(base_dir: nil)
|
|
9
|
+
@schemas = {}
|
|
10
|
+
@base_dir = base_dir
|
|
11
|
+
@resolver = ReferenceResolver.new(@schemas)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.load_from_files(*paths, base_dir: nil)
|
|
15
|
+
set = new(base_dir: base_dir || infer_base_dir(paths))
|
|
16
|
+
paths.each do |path|
|
|
17
|
+
data = File.read(path)
|
|
18
|
+
schema = Schema.from_json(data)
|
|
19
|
+
name = File.basename(path, ".*")
|
|
20
|
+
set.add(name, schema, path)
|
|
21
|
+
end
|
|
22
|
+
set
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.load_from_directory(dir)
|
|
26
|
+
paths = Dir.glob(File.join(dir, "*.json"))
|
|
27
|
+
load_from_files(*paths, base_dir: dir)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def add(name, schema, file_path = nil)
|
|
31
|
+
@schemas[name] = schema
|
|
32
|
+
return unless file_path
|
|
33
|
+
|
|
34
|
+
@file_paths ||= {}
|
|
35
|
+
@file_paths[File.basename(file_path)] = file_path
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def resolve_ref(ref_string, context_schema = nil)
|
|
39
|
+
return nil unless ref_string
|
|
40
|
+
|
|
41
|
+
if ref_string.start_with?("./")
|
|
42
|
+
resolve_file_ref(ref_string, context_schema)
|
|
43
|
+
elsif ref_string.start_with?("http://", "https://")
|
|
44
|
+
resolve_remote_ref(ref_string)
|
|
45
|
+
elsif ref_string.start_with?("#/")
|
|
46
|
+
@resolver.resolve(ref_string, context_schema)
|
|
47
|
+
elsif ref_string.start_with?("#") && !ref_string.start_with?("#/")
|
|
48
|
+
resolve_anchor_ref(ref_string.delete_prefix("#"), context_schema)
|
|
49
|
+
else
|
|
50
|
+
@resolver.resolve(ref_string, context_schema)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def validate!
|
|
55
|
+
errors = []
|
|
56
|
+
seen_refs = Set.new
|
|
57
|
+
|
|
58
|
+
@schemas.each do |name, schema|
|
|
59
|
+
collect_refs(schema, name, errors, seen_refs, "")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
raise ValidationError, errors.join("\n") if errors.any?
|
|
63
|
+
|
|
64
|
+
true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def valid?
|
|
68
|
+
validate! rescue false
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def all_definitions
|
|
72
|
+
@schemas.flat_map do |_name, schema|
|
|
73
|
+
schema.definition_entries
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def all_properties
|
|
78
|
+
@schemas.flat_map do |_name, schema|
|
|
79
|
+
schema.property_entries
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def validation_errors
|
|
84
|
+
errors = []
|
|
85
|
+
@schemas.each do |name, schema|
|
|
86
|
+
collect_refs(schema, name, errors, Set.new, "")
|
|
87
|
+
end
|
|
88
|
+
errors
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def self.infer_base_dir(paths)
|
|
94
|
+
return nil if paths.empty?
|
|
95
|
+
|
|
96
|
+
File.dirname(paths.first)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def resolve_file_ref(ref, context_schema)
|
|
100
|
+
return nil unless context_schema
|
|
101
|
+
|
|
102
|
+
target_file = ref.sub(%r{^\./}, "")
|
|
103
|
+
schema = find_schema_by_filename(target_file)
|
|
104
|
+
return schema if schema
|
|
105
|
+
|
|
106
|
+
# Try loading from base_dir
|
|
107
|
+
if @base_dir
|
|
108
|
+
path = File.join(@base_dir, target_file)
|
|
109
|
+
if File.exist?(path)
|
|
110
|
+
loaded = Schema.from_json(File.read(path))
|
|
111
|
+
add(File.basename(target_file, ".*"), loaded, path)
|
|
112
|
+
return loaded
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def resolve_remote_ref(ref)
|
|
120
|
+
return nil
|
|
121
|
+
|
|
122
|
+
# Remote refs are not resolved — would need HTTP fetching
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def resolve_anchor_ref(anchor, context_schema)
|
|
126
|
+
return nil unless context_schema
|
|
127
|
+
|
|
128
|
+
find_anchor(context_schema, anchor)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def find_anchor(schema, anchor)
|
|
132
|
+
return schema if schema.dollar_anchor == anchor
|
|
133
|
+
|
|
134
|
+
[
|
|
135
|
+
schema.property_entries, schema.definition_entries,
|
|
136
|
+
schema.pattern_property_entries, schema.all_of,
|
|
137
|
+
schema.any_of, schema.one_of
|
|
138
|
+
].each do |entries|
|
|
139
|
+
entries.each do |entry|
|
|
140
|
+
next if entry.nil?
|
|
141
|
+
|
|
142
|
+
child = entry.is_a?(PropertyEntry) ? entry.schema : entry
|
|
143
|
+
next if child.nil?
|
|
144
|
+
|
|
145
|
+
found = find_anchor(child, anchor)
|
|
146
|
+
return found if found
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
[schema.items, schema.not_schema, schema.if_schema,
|
|
151
|
+
schema.then_schema, schema.else_schema].each do |child|
|
|
152
|
+
next if child.nil?
|
|
153
|
+
|
|
154
|
+
found = find_anchor(child, anchor)
|
|
155
|
+
return found if found
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def find_schema_by_filename(filename)
|
|
162
|
+
# Check if we already loaded this file
|
|
163
|
+
@schemas.each do |name, schema|
|
|
164
|
+
return schema if name == File.basename(filename, ".*")
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Check file_paths mapping
|
|
168
|
+
return nil unless @file_paths&.key?(filename)
|
|
169
|
+
|
|
170
|
+
name = File.basename(filename, ".*")
|
|
171
|
+
@schemas[name]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def collect_refs(schema, source_name, errors, seen_refs, path)
|
|
175
|
+
ref = schema.dollar_ref
|
|
176
|
+
if ref && !seen_refs.include?("#{source_name}:#{path}:#{ref}")
|
|
177
|
+
seen_refs.add("#{source_name}:#{path}:#{ref}")
|
|
178
|
+
resolved = resolve_ref(ref, @schemas[source_name])
|
|
179
|
+
errors << "#{source_name}#{path}: unresolvable $ref '#{ref}'" if resolved.nil? && ref.start_with?("#/", "./")
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
children = [
|
|
183
|
+
[:property_entries, schema.property_entries],
|
|
184
|
+
[:definition_entries, schema.definition_entries],
|
|
185
|
+
[:pattern_property_entries, schema.pattern_property_entries],
|
|
186
|
+
]
|
|
187
|
+
children.each do |key, entries|
|
|
188
|
+
entries.each do |entry|
|
|
189
|
+
collect_refs(entry.schema, source_name, errors, seen_refs, "#{path}/#{key}/#{entry.name}")
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
schema.all_of.each_with_index { |s, i| collect_refs(s, source_name, errors, seen_refs, "#{path}/allOf[#{i}]") }
|
|
194
|
+
schema.any_of.each_with_index { |s, i| collect_refs(s, source_name, errors, seen_refs, "#{path}/anyOf[#{i}]") }
|
|
195
|
+
schema.one_of.each_with_index { |s, i| collect_refs(s, source_name, errors, seen_refs, "#{path}/oneOf[#{i}]") }
|
|
196
|
+
|
|
197
|
+
single_children = {
|
|
198
|
+
items: schema.items,
|
|
199
|
+
not_schema: schema.not_schema,
|
|
200
|
+
if_schema: schema.if_schema,
|
|
201
|
+
then_schema: schema.then_schema,
|
|
202
|
+
else_schema: schema.else_schema,
|
|
203
|
+
}
|
|
204
|
+
single_children.each do |attr, child|
|
|
205
|
+
collect_refs(child, source_name, errors, seen_refs, "#{path}/#{attr}") if child
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
schema.links.each do |link|
|
|
209
|
+
collect_refs(link.schema, source_name, errors, seen_refs, "#{path}/link.schema") if link.schema
|
|
210
|
+
collect_refs(link.target_schema, source_name, errors, seen_refs, "#{path}/link.target_schema") if link.target_schema
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
class ValidationError < StandardError; end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Jsonschema
|
|
5
|
+
module Spa
|
|
6
|
+
class Generator
|
|
7
|
+
def initialize(schema_set, output_path, metadata: Metadata.new, strategy: nil)
|
|
8
|
+
@schema_set = schema_set
|
|
9
|
+
@output_path = output_path
|
|
10
|
+
@metadata = metadata
|
|
11
|
+
@strategy = strategy || VueInlinedStrategy.new(@output_path)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def generate
|
|
15
|
+
document = SpaBuilder.new(@schema_set, metadata: @metadata).build
|
|
16
|
+
json_data = document.to_json
|
|
17
|
+
@strategy.write(json_data)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Jsonschema
|
|
5
|
+
module Spa
|
|
6
|
+
class Metadata < Base
|
|
7
|
+
attribute :title, :string
|
|
8
|
+
attribute :version, :string
|
|
9
|
+
attribute :description, :string
|
|
10
|
+
attribute :base_url, :string
|
|
11
|
+
attribute :theme, :string, default: "light"
|
|
12
|
+
|
|
13
|
+
json do
|
|
14
|
+
map "title", to: :title
|
|
15
|
+
map "version", to: :version
|
|
16
|
+
map "description", to: :description
|
|
17
|
+
map "baseUrl", to: :base_url
|
|
18
|
+
map "theme", to: :theme
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Jsonschema
|
|
5
|
+
module Spa
|
|
6
|
+
class OutputStrategy
|
|
7
|
+
def initialize(output_path)
|
|
8
|
+
@output_path = output_path
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def write(_json_data)
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|