tomograph 3.2.7 → 4.0.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/.github/workflows/ruby.yml +1 -1
- data/.rubocop.yml +8 -1
- data/.tool-versions +1 -1
- data/CHANGELOG.md +16 -0
- data/README.md +9 -0
- data/exe/tomograph +17 -0
- data/lib/tomograph/openapi/external_ref_resolver.rb +170 -0
- data/lib/tomograph/openapi/openapi3.rb +129 -14
- data/lib/tomograph/version.rb +1 -1
- data/tomograph.gemspec +3 -3
- metadata +9 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 97a07b518360a088260ba2e9ead6ece87b10235bb7766490bc659277387ef9bc
|
|
4
|
+
data.tar.gz: 239ed051e682a6ea540dc9579f3d58c75594b3a89f4c9ad0fd8c3c4c4ee1b0ae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: adc5d107c07a96565b5ed80d1a6f4ee5069c5ce1785ffa23230494537e73950c24c9455466a30dfd3921575f1c04f81d37bb64037147ff3856cab4570c8ad416
|
|
7
|
+
data.tar.gz: 48260bf6279100f0972b53e1a5192242361c487ffccec6b8d5bf234b1cd2bf1ed72c9a08047d08b54988cf03e80feac54e17b1bc7ab58a49c4b1d3e638fddcc7
|
data/.github/workflows/ruby.yml
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
AllCops:
|
|
2
|
-
TargetRubyVersion:
|
|
2
|
+
TargetRubyVersion: 3.0
|
|
3
|
+
NewCops: disable
|
|
3
4
|
|
|
4
5
|
Layout/LineLength:
|
|
5
6
|
Max: 120
|
|
@@ -30,3 +31,9 @@ Metrics/AbcSize:
|
|
|
30
31
|
|
|
31
32
|
Metrics/ClassLength:
|
|
32
33
|
Max: 150
|
|
34
|
+
|
|
35
|
+
Metrics/CyclomaticComplexity:
|
|
36
|
+
Max: 16
|
|
37
|
+
|
|
38
|
+
Metrics/PerceivedComplexity:
|
|
39
|
+
Max: 18
|
data/.tool-versions
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
ruby
|
|
1
|
+
ruby 3.0.2
|
|
2
2
|
nodejs 11.3.0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Change log
|
|
2
2
|
|
|
3
|
+
### 4.0.0 - 2026-11-03
|
|
4
|
+
|
|
5
|
+
* breaking changes
|
|
6
|
+
* require Ruby 3.0 or newer; Ruby 2.x is no longer supported
|
|
7
|
+
* features
|
|
8
|
+
* resolve external OpenAPI 3 path and component references
|
|
9
|
+
* support multipart and form-urlencoded request schemas
|
|
10
|
+
* bug fixes
|
|
11
|
+
* include referenced component schemas in composed response schemas
|
|
12
|
+
* convert OpenAPI nullable fields to JSON Schema null types
|
|
13
|
+
* ignore path-level parameters when collecting operations
|
|
14
|
+
* preserve optional empty response bodies through `x-empty-body`
|
|
15
|
+
* maintenance
|
|
16
|
+
* align local Ruby version managers and CI on Ruby 3.0.2
|
|
17
|
+
* update RuboCop target Ruby version and complexity limits
|
|
18
|
+
|
|
3
19
|
### 3.2.7 - 2024-12-08
|
|
4
20
|
|
|
5
21
|
* bug fixes
|
data/README.md
CHANGED
|
@@ -371,6 +371,15 @@ Exclude "description" keys from json-schemas.
|
|
|
371
371
|
tomograph -d 4 apielemetns.yaml doc.json --exclude-description
|
|
372
372
|
```
|
|
373
373
|
|
|
374
|
+
#### json-schema-draft4
|
|
375
|
+
|
|
376
|
+
Add `"$schema": "http://json-schema.org/draft-04/schema#"` to every non-empty request and response body.
|
|
377
|
+
Empty bodies stay `{}`.
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
tomograph -d openapi3 openapi3.yaml doc.json --json-schema-draft4
|
|
381
|
+
```
|
|
382
|
+
|
|
374
383
|
#### split
|
|
375
384
|
|
|
376
385
|
Split output into files by method. Output in dir path.
|
data/exe/tomograph
CHANGED
|
@@ -17,6 +17,7 @@ description 'Converts API Blueprint to JSON Schema'
|
|
|
17
17
|
on('-d DRAFTER_VERSION', '--drafter', 'Choose drafter version: crafter or 4,
|
|
18
18
|
or OpenAPI version: openapi2 or openapi3. Default: use drafter v.4.')
|
|
19
19
|
on('--exclude-description', 'Exclude "description" keys.')
|
|
20
|
+
on('--json-schema-draft4', 'Add "$schema": draft-04 to every non-empty request and response body.')
|
|
20
21
|
on('--split', 'Split output into files by method.')
|
|
21
22
|
arg :input, 'path/to/doc.yaml (API Elements)'
|
|
22
23
|
arg :output, 'path/to/doc.json or path/to/dir if --split is used.'
|
|
@@ -30,6 +31,21 @@ def prune!(obj, unwanted_key)
|
|
|
30
31
|
end
|
|
31
32
|
end
|
|
32
33
|
|
|
34
|
+
JSON_SCHEMA_DRAFT4 = 'http://json-schema.org/draft-04/schema#'.freeze
|
|
35
|
+
|
|
36
|
+
def with_draft4(schema)
|
|
37
|
+
return schema unless schema.is_a?(Hash) && !schema.empty?
|
|
38
|
+
|
|
39
|
+
{ '$schema' => JSON_SCHEMA_DRAFT4 }.merge(schema)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def add_draft4!(actions)
|
|
43
|
+
actions.each do |action|
|
|
44
|
+
action['requests'] = action['requests'].map { |request| with_draft4(request) }
|
|
45
|
+
action['responses'].each { |response| response['body'] = with_draft4(response['body']) }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
33
49
|
def choose_drafter(opt_parser)
|
|
34
50
|
case opt_parser
|
|
35
51
|
when 'crafter'
|
|
@@ -80,6 +96,7 @@ main do |input, output|
|
|
|
80
96
|
actions = tomogram.to_a.map(&:to_hash)
|
|
81
97
|
|
|
82
98
|
prune!(actions, 'description') if options['exclude-description']
|
|
99
|
+
add_draft4!(actions) if options['json-schema-draft4']
|
|
83
100
|
|
|
84
101
|
if options['split']
|
|
85
102
|
write_split_json(actions, output)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module Tomograph
|
|
4
|
+
module OpenApi
|
|
5
|
+
# Loads an OpenAPI document and rewrites external $ref (./paths/auth.yaml,
|
|
6
|
+
# ../components/schemas.yaml#/LoginRequest) into internal #/components/... pointers.
|
|
7
|
+
class ExternalRefResolver
|
|
8
|
+
COMPONENT_KINDS = {
|
|
9
|
+
'schema' => 'schemas',
|
|
10
|
+
'schemas' => 'schemas',
|
|
11
|
+
'response' => 'responses',
|
|
12
|
+
'responses' => 'responses',
|
|
13
|
+
'parameter' => 'parameters',
|
|
14
|
+
'parameters' => 'parameters'
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
def self.load(path)
|
|
18
|
+
new.load(path)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
22
|
+
@cache = {}
|
|
23
|
+
@registered = {}
|
|
24
|
+
@stack = []
|
|
25
|
+
@components = {
|
|
26
|
+
'schemas' => {},
|
|
27
|
+
'responses' => {},
|
|
28
|
+
'parameters' => {}
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def load(path)
|
|
33
|
+
path = File.expand_path(path)
|
|
34
|
+
resolved = resolve_node(read_yaml(path), File.dirname(path))
|
|
35
|
+
merge_components(resolved)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def merge_components(doc)
|
|
41
|
+
@components.each do |kind, values|
|
|
42
|
+
next if values.empty?
|
|
43
|
+
|
|
44
|
+
doc['components'] ||= {}
|
|
45
|
+
doc['components'][kind] = values.merge(doc['components'][kind] || {})
|
|
46
|
+
end
|
|
47
|
+
doc
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def resolve_node(node, base_dir)
|
|
51
|
+
case node
|
|
52
|
+
when Hash
|
|
53
|
+
resolved = {}
|
|
54
|
+
node.each { |key, value| resolved[key] = resolve_node(value, base_dir) }
|
|
55
|
+
ref = node['$ref']
|
|
56
|
+
return resolved unless ref.is_a?(String) && external_ref?(ref)
|
|
57
|
+
|
|
58
|
+
target = resolve_external(ref, base_dir)
|
|
59
|
+
extra = resolved.reject { |key, _| key == '$ref' }
|
|
60
|
+
target.is_a?(Hash) ? target.merge(extra) : target
|
|
61
|
+
when Array
|
|
62
|
+
node.map { |value| resolve_node(value, base_dir) }
|
|
63
|
+
else
|
|
64
|
+
node
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def resolve_external(ref, base_dir)
|
|
69
|
+
abs_ref = absolute_ref(ref, base_dir)
|
|
70
|
+
return { '$ref' => ref } if @stack.include?(abs_ref)
|
|
71
|
+
|
|
72
|
+
@stack.push(abs_ref)
|
|
73
|
+
begin
|
|
74
|
+
file, fragment = split_external(ref, base_dir)
|
|
75
|
+
data = read_yaml(file)
|
|
76
|
+
segments = pointer_segments(fragment)
|
|
77
|
+
kind = component_kind(file)
|
|
78
|
+
if kind && segments.length == 1 && data.is_a?(Hash) && data.key?(segments[0])
|
|
79
|
+
register_component_file(file, kind, data)
|
|
80
|
+
{ '$ref' => "#/components/#{kind}/#{segments[0]}" }
|
|
81
|
+
else
|
|
82
|
+
target = segments.empty? ? data : dig_pointer(data, segments)
|
|
83
|
+
resolve_node(deepcopy(target), File.dirname(file))
|
|
84
|
+
end
|
|
85
|
+
ensure
|
|
86
|
+
@stack.pop
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def register_component_file(file, kind, data)
|
|
91
|
+
return if @registered[file]
|
|
92
|
+
|
|
93
|
+
@registered[file] = true
|
|
94
|
+
names = data.keys
|
|
95
|
+
names.each do |name|
|
|
96
|
+
rewritten = rewrite_local_refs(deepcopy(data[name]), names, kind)
|
|
97
|
+
@components[kind][name] = resolve_node(rewritten, File.dirname(file))
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def rewrite_local_refs(node, names, kind)
|
|
102
|
+
case node
|
|
103
|
+
when Hash
|
|
104
|
+
node.each_with_object({}) do |(key, value), result|
|
|
105
|
+
if key == '$ref' && local_component_ref?(value, names)
|
|
106
|
+
result[key] = "#/components/#{kind}/#{value[2..-1]}"
|
|
107
|
+
else
|
|
108
|
+
result[key] = rewrite_local_refs(value, names, kind)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
when Array
|
|
112
|
+
node.map { |value| rewrite_local_refs(value, names, kind) }
|
|
113
|
+
else
|
|
114
|
+
node
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def local_component_ref?(value, names)
|
|
119
|
+
value.is_a?(String) && value =~ /\A#\/([^\/]+)\z/ && names.include?(value[2..-1])
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def external_ref?(ref)
|
|
123
|
+
!ref.start_with?('#')
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def absolute_ref(ref, base_dir)
|
|
127
|
+
file, fragment = ref.split('#', 2)
|
|
128
|
+
"#{File.expand_path(file, base_dir)}##{fragment}"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def split_external(ref, base_dir)
|
|
132
|
+
file, fragment = ref.split('#', 2)
|
|
133
|
+
[File.expand_path(file, base_dir), fragment]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def component_kind(path)
|
|
137
|
+
COMPONENT_KINDS[File.basename(path, '.*')]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def pointer_segments(fragment)
|
|
141
|
+
return [] if fragment.nil? || fragment.empty? || fragment == '/'
|
|
142
|
+
|
|
143
|
+
fragment.sub(%r{\A/}, '').split('/').map { |segment| segment.gsub('~1', '/').gsub('~0', '~') }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def dig_pointer(data, segments)
|
|
147
|
+
segments.reduce(data) do |memo, segment|
|
|
148
|
+
case memo
|
|
149
|
+
when Hash then memo[segment]
|
|
150
|
+
when Array then memo[segment.to_i]
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def read_yaml(path)
|
|
156
|
+
return @cache[path] if @cache.key?(path)
|
|
157
|
+
|
|
158
|
+
@cache[path] = YAML.safe_load(File.read(path)) || {}
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def deepcopy(obj)
|
|
162
|
+
case obj
|
|
163
|
+
when Hash then obj.each_with_object({}) { |(key, value), copy| copy[key] = deepcopy(value) }
|
|
164
|
+
when Array then obj.map { |value| deepcopy(value) }
|
|
165
|
+
else obj
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
require 'tomograph/tomogram/action'
|
|
2
|
+
require 'tomograph/openapi/external_ref_resolver'
|
|
2
3
|
|
|
3
4
|
module Tomograph
|
|
4
5
|
module OpenApi
|
|
5
6
|
class OpenApi3
|
|
7
|
+
HTTP_METHODS = %w[get put post delete options head patch trace].freeze
|
|
8
|
+
REQUEST_SCHEMA_TYPES = ['application/json', 'multipart/form-data', 'application/x-www-form-urlencoded'].freeze
|
|
9
|
+
|
|
6
10
|
def initialize(prefix, openapi3_yaml_path)
|
|
7
11
|
@prefix = prefix
|
|
8
|
-
@documentation =
|
|
12
|
+
@documentation = ExternalRefResolver.load(openapi3_yaml_path)
|
|
9
13
|
end
|
|
10
14
|
|
|
11
15
|
def to_tomogram
|
|
12
16
|
@tomogram ||= @documentation['paths'].each_with_object([]) do |(path, action_definition), result|
|
|
13
|
-
action_definition.
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
next unless action_definition.is_a?(Hash)
|
|
18
|
+
|
|
19
|
+
action_definition.each do |method, operation|
|
|
20
|
+
next unless HTTP_METHODS.include?(method)
|
|
21
|
+
next unless operation.is_a?(Hash)
|
|
16
22
|
|
|
23
|
+
request_body = operation['requestBody']
|
|
24
|
+
content = valuekey(request_body, 'content')
|
|
17
25
|
result.push(Tomograph::Tomogram::Action.new(
|
|
18
26
|
path: "#{@prefix}#{path}",
|
|
19
27
|
method: method.upcase,
|
|
20
|
-
content_type:
|
|
21
|
-
requests: [
|
|
22
|
-
responses: responses(
|
|
28
|
+
content_type: request_body.nil? ? nil : content&.keys&.first,
|
|
29
|
+
requests: [request_schema(content)].compact,
|
|
30
|
+
responses: responses(operation['responses']),
|
|
23
31
|
resource: ''
|
|
24
32
|
))
|
|
25
33
|
end
|
|
@@ -30,8 +38,29 @@ module Tomograph
|
|
|
30
38
|
value.nil? ? nil : value[key]
|
|
31
39
|
end
|
|
32
40
|
|
|
41
|
+
def request_schema(content)
|
|
42
|
+
return if content.nil?
|
|
43
|
+
|
|
44
|
+
type = REQUEST_SCHEMA_TYPES.find { |item| content.key?(item) }
|
|
45
|
+
return if type.nil?
|
|
46
|
+
|
|
47
|
+
node = valuekey(content[type], 'schema')
|
|
48
|
+
return if node.nil?
|
|
49
|
+
|
|
50
|
+
if node.to_s.include?('#/components/schemas/')
|
|
51
|
+
schema(node)
|
|
52
|
+
else
|
|
53
|
+
schema_new(node, @documentation['definitions'])
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
33
57
|
def schema_new(sche, defi)
|
|
58
|
+
json_schema(schema_new_raw(sche, defi))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def schema_new_raw(sche, defi)
|
|
34
62
|
return sche unless sche
|
|
63
|
+
|
|
35
64
|
if sche.keys.include?('$ref')
|
|
36
65
|
res = sche.merge('definitions' => { sche['$ref'][14..-1] => defi[sche['$ref'][14..-1]] })
|
|
37
66
|
if defi[sche['$ref'][14..-1]].to_s.include?('$ref')
|
|
@@ -66,12 +95,22 @@ module Tomograph
|
|
|
66
95
|
if response['content'].nil?
|
|
67
96
|
result.push(
|
|
68
97
|
'status' => response_code,
|
|
69
|
-
'body'=> {},
|
|
98
|
+
'body' => {},
|
|
70
99
|
'content-type' => nil
|
|
71
100
|
)
|
|
72
101
|
else
|
|
73
102
|
result += responses_by_content_types(response['content'], response_code)
|
|
74
103
|
end
|
|
104
|
+
|
|
105
|
+
# API Blueprint allowed a second response with the same status and an
|
|
106
|
+
# empty body. Fitting treats that pair as an optional schema.
|
|
107
|
+
if response['x-empty-body']
|
|
108
|
+
result.push(
|
|
109
|
+
'status' => response_code,
|
|
110
|
+
'body' => {},
|
|
111
|
+
'content-type' => 'application/json'
|
|
112
|
+
)
|
|
113
|
+
end
|
|
75
114
|
end
|
|
76
115
|
result
|
|
77
116
|
end
|
|
@@ -87,6 +126,12 @@ module Tomograph
|
|
|
87
126
|
end
|
|
88
127
|
|
|
89
128
|
def schema(sche)
|
|
129
|
+
json_schema(schema_raw(sche))
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def schema_raw(sche)
|
|
133
|
+
return sche unless sche.is_a?(Hash)
|
|
134
|
+
|
|
90
135
|
if @documentation['components']
|
|
91
136
|
defi = @documentation['components']['schemas']
|
|
92
137
|
elsif @documentation['definitions']
|
|
@@ -122,17 +167,87 @@ module Tomograph
|
|
|
122
167
|
sche
|
|
123
168
|
|
|
124
169
|
elsif sche.to_s.include?('$ref')
|
|
125
|
-
res = sche
|
|
126
|
-
|
|
127
|
-
keys.each do |key|
|
|
128
|
-
res['definitions'].merge!({ key.split('/')[-1] => defi[key.split('/')[-1]] })
|
|
129
|
-
end
|
|
130
|
-
res
|
|
170
|
+
res = attach_definitions(sche, defi)
|
|
171
|
+
attach_component_schemas(res)
|
|
131
172
|
else
|
|
132
173
|
sche
|
|
133
174
|
end
|
|
134
175
|
end
|
|
135
176
|
|
|
177
|
+
def attach_definitions(sche, defi)
|
|
178
|
+
keys = sche.to_s.split('"').find_all { |word| word.include?('definitions') }
|
|
179
|
+
return sche if keys.empty?
|
|
180
|
+
|
|
181
|
+
res = sche.merge('definitions' => {})
|
|
182
|
+
keys.each do |key|
|
|
183
|
+
name = key.split('/')[-1]
|
|
184
|
+
res['definitions'].merge!(name => defi[name])
|
|
185
|
+
end
|
|
186
|
+
res
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def attach_component_schemas(sche)
|
|
190
|
+
schemas = @documentation.dig('components', 'schemas')
|
|
191
|
+
return sche unless schemas
|
|
192
|
+
|
|
193
|
+
copied = {}
|
|
194
|
+
seen = {}
|
|
195
|
+
copy_component_schemas(copied, seen, component_ref_names(sche), schemas)
|
|
196
|
+
return sche if copied.empty?
|
|
197
|
+
|
|
198
|
+
previous = (sche['components'] || {})
|
|
199
|
+
schemas = (previous['schemas'] || {}).merge(copied)
|
|
200
|
+
sche.merge('components' => previous.merge('schemas' => schemas))
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def copy_component_schemas(copied, seen, names, schemas)
|
|
204
|
+
names.each do |name|
|
|
205
|
+
next if seen[name]
|
|
206
|
+
next unless schemas[name]
|
|
207
|
+
|
|
208
|
+
seen[name] = true
|
|
209
|
+
copied[name] = schemas[name]
|
|
210
|
+
copy_component_schemas(copied, seen, component_ref_names(schemas[name]), schemas)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def component_ref_names(schema)
|
|
215
|
+
schema.to_s.split('"').find_all { |word| word.include?('#/components/schemas/') }
|
|
216
|
+
.map { |word| word[21..-1] }
|
|
217
|
+
.uniq
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# OpenAPI 3 `nullable: true` is not a JSON Schema keyword. Draft-04, which
|
|
221
|
+
# the tomogram is validated against, allows null only via the type list.
|
|
222
|
+
def json_schema(node)
|
|
223
|
+
case node
|
|
224
|
+
when Hash
|
|
225
|
+
converted = {}
|
|
226
|
+
node.each { |key, value| converted[key] = json_schema(value) }
|
|
227
|
+
nullable_to_type(converted)
|
|
228
|
+
when Array
|
|
229
|
+
node.map { |value| json_schema(value) }
|
|
230
|
+
else
|
|
231
|
+
node
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def nullable_to_type(schema)
|
|
236
|
+
return schema unless schema.key?('nullable')
|
|
237
|
+
|
|
238
|
+
nullable = schema.delete('nullable')
|
|
239
|
+
return schema unless nullable
|
|
240
|
+
|
|
241
|
+
case schema['type']
|
|
242
|
+
when String
|
|
243
|
+
schema['type'] = [schema['type'], 'null']
|
|
244
|
+
when Array
|
|
245
|
+
schema['type'] = schema['type'].dup
|
|
246
|
+
schema['type'] << 'null' unless schema['type'].include?('null')
|
|
247
|
+
end
|
|
248
|
+
schema
|
|
249
|
+
end
|
|
250
|
+
|
|
136
251
|
def search_hash(hash, key)
|
|
137
252
|
return hash[key] if hash.assoc(key)
|
|
138
253
|
|
data/lib/tomograph/version.rb
CHANGED
data/tomograph.gemspec
CHANGED
|
@@ -5,8 +5,8 @@ require 'tomograph/version'
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = 'tomograph'
|
|
7
7
|
spec.version = Tomograph::VERSION
|
|
8
|
-
spec.authors = ['
|
|
9
|
-
spec.email = ['
|
|
8
|
+
spec.authors = ['Dmitry Efimov']
|
|
9
|
+
spec.email = ['tuwilof@gmail.com']
|
|
10
10
|
|
|
11
11
|
spec.summary = 'Convert API Blueprint, Swagger and OpenAPI to Tomogram'
|
|
12
12
|
spec.description = 'Convert API Blueprint, Swagger and OpenAPI to routes and JSON-Schemas'
|
|
@@ -25,5 +25,5 @@ Gem::Specification.new do |spec|
|
|
|
25
25
|
spec.add_development_dependency 'rspec', '~> 3.10', '>= 3.10.0'
|
|
26
26
|
spec.add_development_dependency 'rubocop', '~> 0.81', '>= 0.81.0'
|
|
27
27
|
spec.add_development_dependency 'simplecov', '~> 0.21', '>= 0.21.2'
|
|
28
|
-
spec.required_ruby_version = '>=
|
|
28
|
+
spec.required_ruby_version = '>= 3.0.0'
|
|
29
29
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tomograph
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
8
|
-
autorequire:
|
|
7
|
+
- Dmitry Efimov
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-11-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: methadone
|
|
@@ -146,7 +146,7 @@ dependencies:
|
|
|
146
146
|
version: 0.21.2
|
|
147
147
|
description: Convert API Blueprint, Swagger and OpenAPI to routes and JSON-Schemas
|
|
148
148
|
email:
|
|
149
|
-
-
|
|
149
|
+
- tuwilof@gmail.com
|
|
150
150
|
executables:
|
|
151
151
|
- tomograph
|
|
152
152
|
extensions: []
|
|
@@ -172,6 +172,7 @@ files:
|
|
|
172
172
|
- lib/tomograph/api_blueprint/drafter_4/yaml.rb
|
|
173
173
|
- lib/tomograph/api_blueprint/drafter_4/yaml/action.rb
|
|
174
174
|
- lib/tomograph/api_blueprint/json_schema.rb
|
|
175
|
+
- lib/tomograph/openapi/external_ref_resolver.rb
|
|
175
176
|
- lib/tomograph/openapi/openapi2.rb
|
|
176
177
|
- lib/tomograph/openapi/openapi3.rb
|
|
177
178
|
- lib/tomograph/path.rb
|
|
@@ -184,7 +185,7 @@ homepage: https://github.com/tuwilof/tomograph
|
|
|
184
185
|
licenses:
|
|
185
186
|
- MIT
|
|
186
187
|
metadata: {}
|
|
187
|
-
post_install_message:
|
|
188
|
+
post_install_message:
|
|
188
189
|
rdoc_options: []
|
|
189
190
|
require_paths:
|
|
190
191
|
- lib
|
|
@@ -192,7 +193,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
192
193
|
requirements:
|
|
193
194
|
- - ">="
|
|
194
195
|
- !ruby/object:Gem::Version
|
|
195
|
-
version:
|
|
196
|
+
version: 3.0.0
|
|
196
197
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
197
198
|
requirements:
|
|
198
199
|
- - ">="
|
|
@@ -200,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
200
201
|
version: '0'
|
|
201
202
|
requirements: []
|
|
202
203
|
rubygems_version: 3.2.22
|
|
203
|
-
signing_key:
|
|
204
|
+
signing_key:
|
|
204
205
|
specification_version: 4
|
|
205
206
|
summary: Convert API Blueprint, Swagger and OpenAPI to Tomogram
|
|
206
207
|
test_files: []
|