openapi-sourcetools 0.12.0 → 0.13
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/bin/openapi-addmediatypes +59 -0
- data/bin/openapi-addrequestbodies +57 -0
- data/bin/openapi-addschemas +30 -14
- data/bin/openapi-generate-checkconfig +73 -0
- data/bin/openapi-logicsplit +96 -0
- data/bin/openapi-order +21 -12
- data/bin/openapi-schema-in +143 -0
- data/bin/openapi-schema-ls +100 -0
- data/bin/openapi-schema-out +144 -0
- data/lib/openapi/sourcetools/apiobjects.rb +19 -13
- data/lib/openapi/sourcetools/common.rb +1 -1
- data/lib/openapi/sourcetools/config.rb +3 -2
- data/lib/openapi/sourcetools/generate.rb +1 -0
- data/lib/openapi/sourcetools/loaders.rb +3 -3
- data/lib/openapi/sourcetools/order.rb +42 -18
- data/lib/openapi/sourcetools/schemaio.rb +264 -0
- data/lib/openapi/sourcetools/task.rb +1 -1
- data/lib/openapi/sourcetools/version.rb +2 -1
- metadata +17 -2
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright © 2026 Ismo Kärkkäinen
|
|
4
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
|
5
|
+
|
|
6
|
+
require_relative 'common'
|
|
7
|
+
require 'yaml'
|
|
8
|
+
require 'json'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Gem namespace.
|
|
12
|
+
module OpenAPISourceTools
|
|
13
|
+
# Methods and classes to get schemas in and out of documents.
|
|
14
|
+
module SchemaIO
|
|
15
|
+
OPENAPI_PATHS = <<EOF
|
|
16
|
+
---
|
|
17
|
+
separator: "/" # For splitting paths.
|
|
18
|
+
ignore: "#" # What to ignore in split paths.
|
|
19
|
+
named: "#/components/schemas" # Before named schema.
|
|
20
|
+
reference: "$ref" # Reference key name.
|
|
21
|
+
referenced: "#/components/schemas" # Reference path before schema name.
|
|
22
|
+
EOF
|
|
23
|
+
|
|
24
|
+
JSON_SCHEMA_PATHS = <<EOF
|
|
25
|
+
---
|
|
26
|
+
separator: "/"
|
|
27
|
+
ignore: "#"
|
|
28
|
+
named: ~
|
|
29
|
+
reference: "$ref"
|
|
30
|
+
referenced: "#/$defs"
|
|
31
|
+
EOF
|
|
32
|
+
|
|
33
|
+
JSON_SCHEMA_DEFS_PATHS = <<EOF
|
|
34
|
+
---
|
|
35
|
+
separator: "/"
|
|
36
|
+
ignore: "#"
|
|
37
|
+
named: "#/$defs"
|
|
38
|
+
reference: "$ref"
|
|
39
|
+
referenced: "#/$defs"
|
|
40
|
+
EOF
|
|
41
|
+
|
|
42
|
+
BUILTINS = {
|
|
43
|
+
openapi: OPENAPI_PATHS,
|
|
44
|
+
jsonschema: JSON_SCHEMA_PATHS,
|
|
45
|
+
jsonschemadefs: JSON_SCHEMA_DEFS_PATHS
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
def self.get(name)
|
|
49
|
+
BUILTINS[name.is_a?(String) ? name.downcase.to_sym : name]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Checks format and does some convenience preparations.
|
|
53
|
+
class SchemaFormat
|
|
54
|
+
attr_reader :separator, :named, :reference, :referenced
|
|
55
|
+
|
|
56
|
+
def initialize(format)
|
|
57
|
+
f = format.is_a?(Hash) ? format : YAML.safe_load(format)
|
|
58
|
+
raise ArgumentError, 'Format is not an object.' unless f.is_a?(Hash)
|
|
59
|
+
errors = []
|
|
60
|
+
@separator = f['separator']
|
|
61
|
+
errors.push('separator must be a non-empty string.') unless @separator.is_a?(String) && !@separator.empty?
|
|
62
|
+
@ignore = f['ignore']
|
|
63
|
+
errors.push('ignore must be a string or an array of strings.') unless @ignore.is_a?(String) || @ignore.is_a?(Array)
|
|
64
|
+
if @ignore.is_a?(Array)
|
|
65
|
+
errors.push('ignore array values must be non-empty strings') unless @ignore.index { |v| !v.is_a?(String) || v.empty? }.nil?
|
|
66
|
+
else
|
|
67
|
+
@ignore = [ @ignore ]
|
|
68
|
+
end
|
|
69
|
+
@ignore.push('')
|
|
70
|
+
@named = f['named']
|
|
71
|
+
@reference = f['reference']
|
|
72
|
+
errors.push('Reference key must be non-empty string.') if @reference.nil?
|
|
73
|
+
@referenced = f['referenced']
|
|
74
|
+
raise ArgumentError, errors.join("\n") unless errors.empty?
|
|
75
|
+
@named = '' if @named.nil?
|
|
76
|
+
@named += @separator unless @named.empty? || @named.end_with?(@separator)
|
|
77
|
+
@referenced += @separator unless @referenced.nil? || @referenced.empty? || @referenced.end_with?(@separator)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def ignore?(str)
|
|
81
|
+
@ignore.include?(str)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def path(str)
|
|
85
|
+
str.split(@separator).reject { |p| ignore?(p) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def reference?(key, value)
|
|
89
|
+
# Accurate enough for now.
|
|
90
|
+
@reference == key && value.is_a?(String) && value.start_with?(@referenced)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def reference_name(ref)
|
|
94
|
+
ref[@referenced.size..]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def make_reference(name)
|
|
98
|
+
"#{@referenced}#{name}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def root_schema?
|
|
102
|
+
@named.empty?
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.load_data(data)
|
|
107
|
+
# File name?
|
|
108
|
+
if data.is_a?(String)
|
|
109
|
+
begin
|
|
110
|
+
return YAML.safe_load_file(data)
|
|
111
|
+
rescue StandardError
|
|
112
|
+
# Was not a file, it is ok.
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
# Internal key?
|
|
116
|
+
d = get(data)
|
|
117
|
+
return YAML.safe_load(d) unless d.nil?
|
|
118
|
+
# Try to parse as JSON.
|
|
119
|
+
if data.is_a?(String)
|
|
120
|
+
begin
|
|
121
|
+
return JSON.parse(data)
|
|
122
|
+
rescue JSON::ParserError
|
|
123
|
+
# Was not.
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def self.key_data(data)
|
|
130
|
+
d = load_data(data)
|
|
131
|
+
d.nil? ? nil : SchemaFormat.new(d)
|
|
132
|
+
rescue ArgumentError => e
|
|
133
|
+
Common.aargh(e.to_s)
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Changes references from source format to target format.
|
|
138
|
+
def self.alter_refs(schema, source_access, target_access)
|
|
139
|
+
if schema.is_a?(Array)
|
|
140
|
+
schema.each do |v|
|
|
141
|
+
alter_refs(v, source_access, target_access)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
return unless schema.is_a?(Hash)
|
|
145
|
+
schema.keys.each do |key|
|
|
146
|
+
value = schema[key]
|
|
147
|
+
if source_access.format.reference?(key, value)
|
|
148
|
+
# Change the prefix in the reference, unless equal.
|
|
149
|
+
unless target_access.format.referenced == source_access.format.referenced
|
|
150
|
+
value = target_access.format.make_reference(source_access.format.reference_name(value))
|
|
151
|
+
end
|
|
152
|
+
# Change reference key name if needed.
|
|
153
|
+
if source_access.format.reference == target_access.format.reference
|
|
154
|
+
schema[key] = value
|
|
155
|
+
else
|
|
156
|
+
schema.delete(key)
|
|
157
|
+
schema[target_access.format.reference] = value
|
|
158
|
+
end
|
|
159
|
+
elsif value.is_a?(Hash) || value.is_a?(Array)
|
|
160
|
+
alter_refs(value, source_access, target_access)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Handles document with schemas in stated places. Reads or modifies.
|
|
166
|
+
class SchemaAccess
|
|
167
|
+
attr_reader :document, :format
|
|
168
|
+
|
|
169
|
+
def initialize(format, document)
|
|
170
|
+
@document = document
|
|
171
|
+
@format = format
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Used when reading from given document.
|
|
175
|
+
|
|
176
|
+
def list_schema_names
|
|
177
|
+
schemas = @document.dig(*@format.path(@format.referenced))
|
|
178
|
+
schemas.keys.sort!
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def find_named_schema(name)
|
|
182
|
+
if @format.root_schema?
|
|
183
|
+
# Make a copy and remove the root key to referenced definitions.
|
|
184
|
+
out = {}.merge!(@document)
|
|
185
|
+
first = @format.path(@format.referenced).first
|
|
186
|
+
out.delete(first) unless first.nil?
|
|
187
|
+
out
|
|
188
|
+
else
|
|
189
|
+
find_by_reference(@format.make_reference(name))
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def find_referenced_schemas(name, schema)
|
|
194
|
+
name2schema = {}
|
|
195
|
+
return name2schema if schema.nil?
|
|
196
|
+
existing = Set.new # Stops loops.
|
|
197
|
+
existing.add(name) unless @format.root_schema?
|
|
198
|
+
todo = [ schema ]
|
|
199
|
+
until todo.empty?
|
|
200
|
+
s = todo.pop
|
|
201
|
+
refs = Set.new
|
|
202
|
+
gather_refs(s, refs, existing)
|
|
203
|
+
# Add all new references to the todo list, if present in the file.
|
|
204
|
+
refs.each do |r|
|
|
205
|
+
existing.add(r)
|
|
206
|
+
idx = r.rindex(@format.separator)
|
|
207
|
+
next if idx.nil?
|
|
208
|
+
refd = find_by_reference(r)
|
|
209
|
+
next if refd.nil?
|
|
210
|
+
todo.push(refd)
|
|
211
|
+
name2schema[r[(idx + 1)..]] = todo.last
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
name2schema
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Used when writing to given document.
|
|
218
|
+
|
|
219
|
+
def put_named_schema(name, schema)
|
|
220
|
+
if @format.root_schema?
|
|
221
|
+
# If clear was to be done we'd need to retain "$defs" or such.
|
|
222
|
+
# In intended use, JSON schema document is empty when writing to root.
|
|
223
|
+
@document.merge!(schema)
|
|
224
|
+
else
|
|
225
|
+
path = make_path(@format.named, name)
|
|
226
|
+
OpenAPISourceTools::Common.bury(@document, path, schema)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Call only after put_named_schema.
|
|
231
|
+
def put_referenced_schema(name, schema)
|
|
232
|
+
path = make_path(@format.referenced, name)
|
|
233
|
+
OpenAPISourceTools::Common.bury(@document, path, schema)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
private
|
|
237
|
+
|
|
238
|
+
def make_path(start, name)
|
|
239
|
+
@format.path(start).push(name)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def find_by_reference(ref)
|
|
243
|
+
# Implicitly expects that references always have same start followed by name.
|
|
244
|
+
@document.dig(*@format.path(ref))
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def gather_refs(schema, found, existing)
|
|
248
|
+
if schema.is_a?(Array)
|
|
249
|
+
schema.each do |v|
|
|
250
|
+
gather_refs(v, found, existing)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
return unless schema.is_a?(Hash)
|
|
254
|
+
schema.each do |key, value|
|
|
255
|
+
if @format.reference?(key, value)
|
|
256
|
+
found.add(value) unless existing.member?(value)
|
|
257
|
+
elsif value.is_a?(Hash) || value.is_a?(Array)
|
|
258
|
+
gather_refs(value, found, existing)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
# Copyright © 2025-2026 Ismo Kärkkäinen
|
|
4
4
|
# Licensed under Universal Permissive License. See LICENSE.txt.
|
|
5
5
|
|
|
6
|
+
# Gem namespace.
|
|
6
7
|
module OpenAPISourceTools
|
|
7
8
|
NAME = 'openapi-sourcetools'
|
|
8
|
-
VERSION = '0.
|
|
9
|
+
VERSION = '0.13'
|
|
9
10
|
|
|
10
11
|
def self.info(separator = ': ')
|
|
11
12
|
"#{NAME}#{separator}#{VERSION}"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openapi-sourcetools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: '0.13'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ismo Kärkkäinen
|
|
@@ -37,7 +37,9 @@ description: |-
|
|
|
37
37
|
email: ismokarkkainen@icloud.com
|
|
38
38
|
executables:
|
|
39
39
|
- openapi-addheaders
|
|
40
|
+
- openapi-addmediatypes
|
|
40
41
|
- openapi-addparameters
|
|
42
|
+
- openapi-addrequestbodies
|
|
41
43
|
- openapi-addresponses
|
|
42
44
|
- openapi-addschemas
|
|
43
45
|
- openapi-addsecurityschemes
|
|
@@ -45,17 +47,24 @@ executables:
|
|
|
45
47
|
- openapi-clearrefs
|
|
46
48
|
- openapi-frequencies
|
|
47
49
|
- openapi-generate
|
|
50
|
+
- openapi-generate-checkconfig
|
|
51
|
+
- openapi-logicsplit
|
|
48
52
|
- openapi-merge
|
|
49
53
|
- openapi-modifypaths
|
|
50
54
|
- openapi-order
|
|
51
55
|
- openapi-patterntests
|
|
52
56
|
- openapi-processpaths
|
|
57
|
+
- openapi-schema-in
|
|
58
|
+
- openapi-schema-ls
|
|
59
|
+
- openapi-schema-out
|
|
53
60
|
extensions: []
|
|
54
61
|
extra_rdoc_files: []
|
|
55
62
|
files:
|
|
56
63
|
- LICENSE.txt
|
|
57
64
|
- bin/openapi-addheaders
|
|
65
|
+
- bin/openapi-addmediatypes
|
|
58
66
|
- bin/openapi-addparameters
|
|
67
|
+
- bin/openapi-addrequestbodies
|
|
59
68
|
- bin/openapi-addresponses
|
|
60
69
|
- bin/openapi-addschemas
|
|
61
70
|
- bin/openapi-addsecurityschemes
|
|
@@ -63,11 +72,16 @@ files:
|
|
|
63
72
|
- bin/openapi-clearrefs
|
|
64
73
|
- bin/openapi-frequencies
|
|
65
74
|
- bin/openapi-generate
|
|
75
|
+
- bin/openapi-generate-checkconfig
|
|
76
|
+
- bin/openapi-logicsplit
|
|
66
77
|
- bin/openapi-merge
|
|
67
78
|
- bin/openapi-modifypaths
|
|
68
79
|
- bin/openapi-order
|
|
69
80
|
- bin/openapi-patterntests
|
|
70
81
|
- bin/openapi-processpaths
|
|
82
|
+
- bin/openapi-schema-in
|
|
83
|
+
- bin/openapi-schema-ls
|
|
84
|
+
- bin/openapi-schema-out
|
|
71
85
|
- lib/openapi/sourcetools.rb
|
|
72
86
|
- lib/openapi/sourcetools/apiobjects.rb
|
|
73
87
|
- lib/openapi/sourcetools/common.rb
|
|
@@ -79,6 +93,7 @@ files:
|
|
|
79
93
|
- lib/openapi/sourcetools/loaders.rb
|
|
80
94
|
- lib/openapi/sourcetools/order.rb
|
|
81
95
|
- lib/openapi/sourcetools/output.rb
|
|
96
|
+
- lib/openapi/sourcetools/schemaio.rb
|
|
82
97
|
- lib/openapi/sourcetools/task.rb
|
|
83
98
|
- lib/openapi/sourcetools/version.rb
|
|
84
99
|
homepage: https://xn--ismo-krkkinen-gfbd.fi/openapi-sourcetools/index.html
|
|
@@ -100,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
100
115
|
- !ruby/object:Gem::Version
|
|
101
116
|
version: '0'
|
|
102
117
|
requirements: []
|
|
103
|
-
rubygems_version:
|
|
118
|
+
rubygems_version: 4.0.21
|
|
104
119
|
specification_version: 4
|
|
105
120
|
summary: Tools for creating source code from API specification.
|
|
106
121
|
test_files: []
|