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.
@@ -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
@@ -7,8 +7,8 @@ require_relative 'common'
7
7
  require 'erb'
8
8
 
9
9
 
10
+ # Gem namespace.
10
11
  module OpenAPISourceTools
11
-
12
12
  # Required interface for tasks, with default implementation for some methods.
13
13
  module TaskInterface
14
14
  def generate(context_binding)
@@ -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.12.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.12.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: 3.6.9
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: []