openapi-sourcetools 0.8.1 → 0.9.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 +4 -4
- data/bin/openapi-patterntests +159 -0
- data/lib/openapi/sourcetools/apiobjects.rb +64 -0
- data/lib/openapi/sourcetools/helper.rb +41 -0
- data/lib/openapi/sourcetools/output.rb +3 -1
- data/lib/openapi/sourcetools/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44ff7d64b4781fdfbab07060f6faa294a8092e342530e6cde786e50ff8f69db1
|
4
|
+
data.tar.gz: 893e593407c4734e67c83bffe77a67bb55b2ecef3970620a416580fd33109437
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 741c12063a6d5df5d090f26b80c60a7810083537076573cb10f9ba5a3ca3afc8109ab6faa305707139c881a7efa1f478d7d0de562db67b44f93c0329ae8263e4
|
7
|
+
data.tar.gz: 9e1c2bc87442258d75517beb13f99c1e0b210b7074ee622db8057e1e4ebf17c253538ba52b893b0f148baa21188a306976a9783d778e92dd769caee6eb84aef6
|
@@ -0,0 +1,159 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Copyright © 2025 Ismo Kärkkäinen
|
5
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
6
|
+
|
7
|
+
require_relative '../lib/openapi/sourcetools/common'
|
8
|
+
require 'optparse'
|
9
|
+
include OpenAPISourceTools
|
10
|
+
|
11
|
+
def key(item)
|
12
|
+
"#{item['pattern']}::#{item.fetch('minLength', 0)}::#{item.fetch('maxLength', 'inf')}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_patterns(doc, pmm)
|
16
|
+
if doc.is_a?(Hash)
|
17
|
+
if doc.key?('pattern')
|
18
|
+
item = { 'pattern' => doc['pattern'] }
|
19
|
+
item['minLength'] = doc['minLength'] if doc.key?('minLength')
|
20
|
+
item['maxLength'] = doc['maxLength'] if doc.key?('maxLength')
|
21
|
+
pmm[key(doc)] = item
|
22
|
+
return
|
23
|
+
end
|
24
|
+
doc = doc.values
|
25
|
+
end
|
26
|
+
doc.each { |v| find_patterns(v, pmm) } if doc.is_a?(Array)
|
27
|
+
end
|
28
|
+
|
29
|
+
def pattern_list2hash(list)
|
30
|
+
pmms = {}
|
31
|
+
list.each { |item| pmms[key(item)] = item }
|
32
|
+
pmms
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_strings(item)
|
36
|
+
passes = []
|
37
|
+
fails = []
|
38
|
+
min_len = item.fetch('minLength', 0)
|
39
|
+
fails.push('f' * (min_len - 1)) unless min_len.zero?
|
40
|
+
max_len = item['maxLength']
|
41
|
+
fails.push('f' * (max_len + 1)) unless max_len.nil?
|
42
|
+
# Fails within length limits require considering the pattern.
|
43
|
+
# All passes require considering the pattern.
|
44
|
+
item['pass'] = passes
|
45
|
+
item['fail'] = fails
|
46
|
+
end
|
47
|
+
|
48
|
+
def merge_arrays(current, past)
|
49
|
+
if current.is_a?(Array)
|
50
|
+
if past.is_a?(Array)
|
51
|
+
return current.concat(past)
|
52
|
+
end
|
53
|
+
current
|
54
|
+
elsif past.is_a?(Array)
|
55
|
+
past
|
56
|
+
else
|
57
|
+
false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def merge_existing(pmms, existing)
|
62
|
+
pmms.each do |k, v|
|
63
|
+
ex = existing[k]
|
64
|
+
next if ex.nil?
|
65
|
+
%w[pass fail].each do |arr|
|
66
|
+
m = merge_arrays(v[arr], ex[arr])
|
67
|
+
v[arr] = m.is_a?(Array) ? m.sort!.uniq : m
|
68
|
+
end
|
69
|
+
ex.each do |ek, ev|
|
70
|
+
v[ek] = ev unless v.key?(ek)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def add_removed(pmms, existing)
|
76
|
+
existing.each { |k, v| pmms[k] = v unless pmms.key?(k) }
|
77
|
+
end
|
78
|
+
|
79
|
+
def compare(a, b)
|
80
|
+
d = a['pattern'] <=> b['pattern']
|
81
|
+
return d unless d.zero?
|
82
|
+
d = a.fetch('minLength', 0) <=> b.fetch('minLength', 0)
|
83
|
+
return d unless d.zero?
|
84
|
+
a.fetch('maxLength', Float::INFINITY) <=> b.fetch('maxLength', Float::INFINITY)
|
85
|
+
end
|
86
|
+
|
87
|
+
def pattern_hash2list(pmms)
|
88
|
+
pmms.values.sort { |a, b| compare(a, b) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def main
|
92
|
+
array_name = 'patterns'
|
93
|
+
input_name = nil
|
94
|
+
output_name = nil
|
95
|
+
source_tests = nil
|
96
|
+
keep = false
|
97
|
+
chain = []
|
98
|
+
|
99
|
+
parser = OptionParser.new do |opts|
|
100
|
+
opts.summary_indent = ' '
|
101
|
+
opts.summary_width = 20
|
102
|
+
opts.banner = 'Usage: openapi-patterntests [options]'
|
103
|
+
opts.separator ''
|
104
|
+
opts.separator 'Options:'
|
105
|
+
opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
|
106
|
+
input_name = f
|
107
|
+
end
|
108
|
+
opts.on('-o', '--output FILE', 'Output to FILE, not stdout.') do |f|
|
109
|
+
output_name = f
|
110
|
+
end
|
111
|
+
opts.on('-t', '--tests FILE', 'Read existing tests from FILE.') do |f|
|
112
|
+
source_tests = f
|
113
|
+
end
|
114
|
+
opts.on('-u', '--under STR', %(Top-level "#{array_name}" is under dot-separated keys.)) do |s|
|
115
|
+
chain = s.split('.').reject(&:empty?)
|
116
|
+
end
|
117
|
+
opts.on('-k', '--[no-]keep', "Keep missing test patterns, default = #{Common.yesno(keep)}.") do |b|
|
118
|
+
keep = b
|
119
|
+
end
|
120
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
121
|
+
$stdout.puts %(#{opts}
|
122
|
+
|
123
|
+
Loads API document in OpenAPI format, extracts string patterns, and outputs
|
124
|
+
a YAML file that contains mapping from patterns to matching and not mathcing
|
125
|
+
strings for testing generated code.
|
126
|
+
)
|
127
|
+
exit 0
|
128
|
+
end
|
129
|
+
end
|
130
|
+
parser.order!
|
131
|
+
|
132
|
+
doc = Common.load_source(input_name)
|
133
|
+
return 2 if doc.nil?
|
134
|
+
|
135
|
+
if source_tests.nil?
|
136
|
+
ex = {}
|
137
|
+
pats = []
|
138
|
+
else
|
139
|
+
ex = Common.load_source(source_tests)
|
140
|
+
return 2 if ex.nil?
|
141
|
+
parent = chain.empty? ? ex : ex.dig(*chain)
|
142
|
+
return Common.aargh("Key chain #{chain.join('.')} not found in source tests.", 4) if parent.nil?
|
143
|
+
pats = parent[array_name] || []
|
144
|
+
end
|
145
|
+
chain.push(array_name)
|
146
|
+
|
147
|
+
existing = pattern_list2hash(pats)
|
148
|
+
pmms = {}
|
149
|
+
find_patterns(doc, pmms)
|
150
|
+
pmms.each_value { |item| add_strings(item) }
|
151
|
+
merge_existing(pmms, existing)
|
152
|
+
add_removed(pmms, existing) if keep
|
153
|
+
pats = pattern_hash2list(pmms)
|
154
|
+
|
155
|
+
Common.bury(ex, chain, pats)
|
156
|
+
Common.dump_result(output_name, ex, 3)
|
157
|
+
end
|
158
|
+
|
159
|
+
exit(main) if defined?($unit_test).nil?
|
@@ -187,5 +187,69 @@ module OpenAPISourceTools
|
|
187
187
|
end
|
188
188
|
out
|
189
189
|
end
|
190
|
+
|
191
|
+
# Single server variable object.
|
192
|
+
class ServerVariableObject
|
193
|
+
include Comparable
|
194
|
+
|
195
|
+
attr_reader :name, :default, :enum
|
196
|
+
|
197
|
+
def initialize(name, variable_object)
|
198
|
+
@name = name
|
199
|
+
@default = variable_object['default']
|
200
|
+
@enum = (variable_object['enum'] || []).sort!
|
201
|
+
end
|
202
|
+
|
203
|
+
def <=>(other)
|
204
|
+
d = @name <=> other.name
|
205
|
+
return d unless d.zero?
|
206
|
+
d = @default <=> other.default
|
207
|
+
return d unless d.zero?
|
208
|
+
@enum <=> other.enum
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Single server object with variables.
|
213
|
+
class ServerObject
|
214
|
+
include Comparable
|
215
|
+
|
216
|
+
attr_reader :url, :variables
|
217
|
+
|
218
|
+
def initialize(server_object)
|
219
|
+
@url = server_object['url']
|
220
|
+
vs = server_object['variables'] || {}
|
221
|
+
@variables = vs.keys.sort!.map do |name|
|
222
|
+
obj = vs[name]
|
223
|
+
ServerVariableObject.new(name, obj)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def <=>(other)
|
228
|
+
d = @url <=> other.url
|
229
|
+
return d unless d.zero?
|
230
|
+
if @variables.nil? || other.variables.nil?
|
231
|
+
return -1 if @variables.nil?
|
232
|
+
return 1 if other.variables.nil?
|
233
|
+
end
|
234
|
+
@variables <=> other.variables
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Combines servers array with set identifier.
|
239
|
+
class ServerAlternatives
|
240
|
+
include Comparable
|
241
|
+
|
242
|
+
attr_reader :servers
|
243
|
+
attr_accessor :set_id
|
244
|
+
|
245
|
+
def initialize(server_objects)
|
246
|
+
@servers = server_objects.map { |so| ServerObject.new(so) }
|
247
|
+
@servers.sort!
|
248
|
+
end
|
249
|
+
|
250
|
+
def <=>(other)
|
251
|
+
@servers <=> other.servers
|
252
|
+
end
|
253
|
+
end
|
190
254
|
end
|
191
255
|
end
|
@@ -76,6 +76,47 @@ module OpenAPISourceTools
|
|
76
76
|
end
|
77
77
|
uniqs.keys.sort!.map { |k| uniqs[k] }
|
78
78
|
end
|
79
|
+
|
80
|
+
def response_codes(responses_object)
|
81
|
+
responses_object.keys.sort! do |a, b|
|
82
|
+
ad = a.downcase
|
83
|
+
bd = b.downcase
|
84
|
+
if ad == 'default'
|
85
|
+
1
|
86
|
+
elsif bd == 'default'
|
87
|
+
-1
|
88
|
+
else
|
89
|
+
ax = ad.end_with?('x')
|
90
|
+
bx = bd.end_with?('x')
|
91
|
+
if ax && bx || !ax && !bx
|
92
|
+
a <=> b # Both numbers or patterns.
|
93
|
+
else
|
94
|
+
ax ? 1 : -1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def response_code_condition(code, var: 'code', op_and: '&&', op_lte: '<=', op_eq: '==')
|
101
|
+
low = []
|
102
|
+
high = []
|
103
|
+
code.downcase.each_char do |c|
|
104
|
+
if c == 'x'
|
105
|
+
low.push('0')
|
106
|
+
high.push('9')
|
107
|
+
else
|
108
|
+
low.push(c)
|
109
|
+
high.push(c)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
low = low.join
|
113
|
+
high = high.join
|
114
|
+
if low == high
|
115
|
+
"#{var} #{op_eq} #{low}"
|
116
|
+
else
|
117
|
+
"(#{low} #{op_lte} #{var}) #{op_and} (#{var} #{op_lte} #{high})"
|
118
|
+
end
|
119
|
+
end
|
79
120
|
end
|
80
121
|
|
81
122
|
# Task class to add an Helper instance to Gen.h, for convenience.
|
@@ -46,13 +46,15 @@ module OpenAPISourceTools
|
|
46
46
|
indent = 0
|
47
47
|
blocks.each do |block|
|
48
48
|
if block.nil?
|
49
|
-
|
49
|
+
next
|
50
50
|
elsif block.is_a?(Integer)
|
51
51
|
indent += block
|
52
|
+
indent = 0 if indent.negative?
|
52
53
|
elsif block.is_a?(TrueClass)
|
53
54
|
indent += @config.indent_step
|
54
55
|
elsif block.is_a?(FalseClass)
|
55
56
|
indent -= @config.indent_step
|
57
|
+
indent = 0 if indent.negative?
|
56
58
|
else
|
57
59
|
block = block.to_s unless block.is_a?(String)
|
58
60
|
if block.empty?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openapi-sourcetools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ismo Kärkkäinen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-04-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: deep_merge
|
@@ -47,6 +47,7 @@ executables:
|
|
47
47
|
- openapi-generate
|
48
48
|
- openapi-merge
|
49
49
|
- openapi-modifypaths
|
50
|
+
- openapi-patterntests
|
50
51
|
- openapi-processpaths
|
51
52
|
extensions: []
|
52
53
|
extra_rdoc_files: []
|
@@ -62,6 +63,7 @@ files:
|
|
62
63
|
- bin/openapi-generate
|
63
64
|
- bin/openapi-merge
|
64
65
|
- bin/openapi-modifypaths
|
66
|
+
- bin/openapi-patterntests
|
65
67
|
- bin/openapi-processpaths
|
66
68
|
- lib/openapi/sourcetools.rb
|
67
69
|
- lib/openapi/sourcetools/apiobjects.rb
|