open_api-loader 0.0.1
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/.codeclimate.yml +15 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +28 -0
- data/.travis.yml +24 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +99 -0
- data/Rakefile +10 -0
- data/bin/console +6 -0
- data/bin/setup +6 -0
- data/lib/open_api-loader.rb +1 -0
- data/lib/open_api/loader.rb +44 -0
- data/lib/open_api/loader/collector.rb +61 -0
- data/lib/open_api/loader/denormalizer.rb +27 -0
- data/lib/open_api/loader/denormalizer/parameters.rb +46 -0
- data/lib/open_api/loader/denormalizer/security.rb +35 -0
- data/lib/open_api/loader/denormalizer/servers.rb +36 -0
- data/lib/open_api/loader/denormalizer/variables.rb +54 -0
- data/lib/open_api/loader/reader.rb +47 -0
- data/lib/open_api/loader/ref.rb +85 -0
- data/lib/open_api/loader/translator.rb +50 -0
- data/lib/open_api/loader/translator/clean_definitions.rb +16 -0
- data/lib/open_api/loader/translator/convert_bodies.rb +77 -0
- data/lib/open_api/loader/translator/convert_forms.rb +71 -0
- data/lib/open_api/loader/translator/convert_parameters.rb +135 -0
- data/lib/open_api/loader/translator/convert_responses.rb +63 -0
- data/lib/open_api/loader/translator/convert_security_schemes.rb +49 -0
- data/lib/open_api/loader/translator/convert_servers.rb +46 -0
- data/lib/open_api/loader/translator/convert_version.rb +12 -0
- data/lib/open_api/loader/translator/denormalize_consumes.rb +44 -0
- data/lib/open_api/loader/translator/denormalize_parameters.rb +55 -0
- data/lib/open_api/loader/translator/denormalize_produces.rb +53 -0
- data/open_api-loader.gemspec +25 -0
- data/spec/fixtures/oas2/collected.yaml +1012 -0
- data/spec/fixtures/oas2/denormalized.yaml +1462 -0
- data/spec/fixtures/oas2/loaded.yaml +564 -0
- data/spec/fixtures/oas2/models.yaml +118 -0
- data/spec/fixtures/oas2/source.json +1 -0
- data/spec/fixtures/oas2/source.yaml +569 -0
- data/spec/fixtures/oas2/translated.yaml +1396 -0
- data/spec/fixtures/oas3/collected.yaml +233 -0
- data/spec/fixtures/oas3/denormalized.yaml +233 -0
- data/spec/fixtures/oas3/source.json +1 -0
- data/spec/fixtures/oas3/source.yaml +217 -0
- data/spec/loader_spec.rb +53 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/fixture_helpers.rb +18 -0
- data/spec/support/path_helpers.rb +27 -0
- data/spec/unit/collector_spec.rb +31 -0
- data/spec/unit/denormalizer_spec.rb +17 -0
- data/spec/unit/reader_spec.rb +53 -0
- data/spec/unit/ref_spec.rb +107 -0
- data/spec/unit/translator_spec.rb +15 -0
- metadata +232 -0
@@ -0,0 +1,135 @@
|
|
1
|
+
class OpenAPI::Loader::Translator
|
2
|
+
#
|
3
|
+
# Adds 'schema', 'style' and 'explode' to values of 'parameters'.
|
4
|
+
#
|
5
|
+
# @private
|
6
|
+
#
|
7
|
+
class ConvertParameters < SimpleDelegator
|
8
|
+
def call
|
9
|
+
parameters.each { |parameter| convert(parameter) }
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def parameters
|
15
|
+
Enumerator.new do |yielder|
|
16
|
+
root_parameters.each { |item| yielder << item }
|
17
|
+
path_parameters.each { |item| yielder << item }
|
18
|
+
operation_parameters.each { |item| yielder << item }
|
19
|
+
headers.each { |item| yielder << item }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def convert(item)
|
24
|
+
place = item["in"]
|
25
|
+
format = item.delete "collectionFormat"
|
26
|
+
style = style(place, format)
|
27
|
+
explode = explode(format)
|
28
|
+
schema = schema(item)
|
29
|
+
|
30
|
+
item.update("schema" => schema).update(style).update(explode)
|
31
|
+
end
|
32
|
+
|
33
|
+
def params(item)
|
34
|
+
Array item.fetch("parameters", [])
|
35
|
+
end
|
36
|
+
|
37
|
+
def root_parameters
|
38
|
+
Enumerator.new do |yielder|
|
39
|
+
params(self).each { |item| yielder << item if item.is_a? Hash }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def path_parameters
|
44
|
+
Enumerator.new do |yielder|
|
45
|
+
paths.each do |path|
|
46
|
+
params(path).each { |item| yielder << item if item.is_a? Hash }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def operation_parameters
|
52
|
+
Enumerator.new do |yielder|
|
53
|
+
operations.each do |operation|
|
54
|
+
params(operation).each { |item| yielder << item if item.is_a? Hash }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def paths
|
60
|
+
Enumerator.new do |yielder|
|
61
|
+
fetch("paths", {}).each_value do |path|
|
62
|
+
yielder << path if path.is_a? Hash
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def operations
|
68
|
+
Enumerator.new do |yielder|
|
69
|
+
paths.each do |path|
|
70
|
+
path.each_value { |item| yielder << item if item.is_a? Hash }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def responses
|
76
|
+
Enumerator.new do |yielder|
|
77
|
+
operations.each do |operation|
|
78
|
+
operation.fetch("responses", {}).each_value do |item|
|
79
|
+
yielder << item if item.is_a? Hash
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def headers
|
86
|
+
Enumerator.new do |yielder|
|
87
|
+
responses.each do |response|
|
88
|
+
response.fetch("headers", {}).each_value do |item|
|
89
|
+
yielder << item if item.is_a? Hash
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def style(place, format)
|
96
|
+
case format
|
97
|
+
when "csv"
|
98
|
+
{ "style" => (%w[query cookie].include?(place) ? "form" : "simple") }
|
99
|
+
when "ssv" then { "style" => "spaceDelimited" }
|
100
|
+
when "pipes" then { "style" => "pipeDelimited" }
|
101
|
+
when "multi" then { "style" => "form" }
|
102
|
+
else {}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def explode(format)
|
107
|
+
format == "multi" ? { "explode" => true } : {}
|
108
|
+
end
|
109
|
+
|
110
|
+
def schema(item)
|
111
|
+
SCHEMA_KEYS.each_with_object({}) do |key, obj|
|
112
|
+
obj[key] = item.delete(key) if item.key? key
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
SCHEMA_KEYS = %w[
|
117
|
+
default
|
118
|
+
enum
|
119
|
+
exclusiveMaximum
|
120
|
+
exclusiveMinimum
|
121
|
+
format
|
122
|
+
items
|
123
|
+
maxItems
|
124
|
+
maxLength
|
125
|
+
maximum
|
126
|
+
minItems
|
127
|
+
minLength
|
128
|
+
minimum
|
129
|
+
multipleOf
|
130
|
+
pattern
|
131
|
+
type
|
132
|
+
uniqueItems
|
133
|
+
].freeze
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class OpenAPI::Loader::Translator
|
2
|
+
#
|
3
|
+
# Builds 'response.content' from 'schema' and 'produces' parameters
|
4
|
+
#
|
5
|
+
# @private
|
6
|
+
#
|
7
|
+
class ConvertResponses < SimpleDelegator
|
8
|
+
def call
|
9
|
+
responses.each { |response| convert(response) }
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def paths
|
15
|
+
Enumerator.new do |yielder|
|
16
|
+
fetch("paths", {}).each_value do |path|
|
17
|
+
yielder << path if path.is_a? Hash
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def operations
|
23
|
+
Enumerator.new do |yielder|
|
24
|
+
paths.each do |path|
|
25
|
+
path.each_value { |item| yielder << item if item.is_a? Hash }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def responses
|
31
|
+
Enumerator.new do |yielder|
|
32
|
+
operations.each do |operation|
|
33
|
+
responses = operation["responses"]
|
34
|
+
next unless responses.is_a? Hash
|
35
|
+
responses.each_value { |item| yielder << item if item.is_a? Hash }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def convert(response)
|
41
|
+
content_types = Array response.delete("produces")
|
42
|
+
schema = Hash response.delete("schema")
|
43
|
+
return if content_types.empty?
|
44
|
+
response["content"] = {}
|
45
|
+
content_types.each do |type|
|
46
|
+
response["content"][type] = {
|
47
|
+
"schema" => type["/xml"] ? schema : drop_xml(schema)
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def drop_xml(data)
|
53
|
+
case data
|
54
|
+
when Hash
|
55
|
+
data.each_with_object({}) do |(key, val), obj|
|
56
|
+
obj[key] = drop_xml(val) unless key == "xml"
|
57
|
+
end
|
58
|
+
when Array then data.map { |item| drop_xml(item) }
|
59
|
+
else data
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class OpenAPI::Loader::Translator
|
2
|
+
#
|
3
|
+
# Wraps the schema and moves its securityDefinitions
|
4
|
+
# to components.securitySchemes
|
5
|
+
#
|
6
|
+
# Translates every securityScheme for oauth2 into new format.
|
7
|
+
#
|
8
|
+
# @private
|
9
|
+
#
|
10
|
+
class ConvertSecuritySchemes < SimpleDelegator
|
11
|
+
def call
|
12
|
+
return unless schemes.is_a? Hash
|
13
|
+
convert_oauth_schemes
|
14
|
+
self["components"] = { "securitySchemes" => schemes }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def schemes
|
20
|
+
@schemes ||= delete("securityDefinitions")
|
21
|
+
end
|
22
|
+
|
23
|
+
def oauth_keys
|
24
|
+
@oauth_keys ||= schemes.select do |_, scheme|
|
25
|
+
scheme.is_a?(Hash) && scheme["type"] == "oauth2"
|
26
|
+
end.keys
|
27
|
+
end
|
28
|
+
|
29
|
+
def convert_oauth_schemes
|
30
|
+
oauth_keys.each { |key| convert_scheme(key) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def convert_scheme(key)
|
34
|
+
scheme = schemes[key]
|
35
|
+
flow = scheme["flow"]
|
36
|
+
flow = FLOWS.fetch(flow, flow)
|
37
|
+
data = scheme.select { |detail| DETAILS.include? detail }
|
38
|
+
|
39
|
+
schemes[key] = { "type" => "oauth2", "flows" => { flow => data } }
|
40
|
+
end
|
41
|
+
|
42
|
+
FLOWS = {
|
43
|
+
"application" => "clientCredentials",
|
44
|
+
"accessCode" => "authorizationCode",
|
45
|
+
}.freeze
|
46
|
+
|
47
|
+
DETAILS = %w[authorizationUrl tokenUrl scopes]
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class OpenAPI::Loader::Translator
|
2
|
+
#
|
3
|
+
# Translates OAS2 host, basePath and schemes to OAS3 servers
|
4
|
+
# Mutates the source
|
5
|
+
#
|
6
|
+
# @private
|
7
|
+
#
|
8
|
+
class ConvertServers < SimpleDelegator
|
9
|
+
def call
|
10
|
+
convert self
|
11
|
+
paths.each { |item| convert item }
|
12
|
+
operations.each { |item| convert item }
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def paths
|
18
|
+
@paths ||= Enumerator.new do |yielder|
|
19
|
+
fetch("paths", {}).each_value do |path|
|
20
|
+
path.each_value { |item| yielder << item if item.is_a? Hash }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def operations
|
26
|
+
@operations ||= Enumerator.new do |yielder|
|
27
|
+
paths.each do |path|
|
28
|
+
path.each_value { |item| yielder << item if item.is_a? Hash }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def url
|
34
|
+
@url ||= File.join delete("host"), delete("basePath")
|
35
|
+
end
|
36
|
+
|
37
|
+
def convert(item)
|
38
|
+
enum = item.delete "schemes"
|
39
|
+
return unless enum
|
40
|
+
item["servers"] = [{
|
41
|
+
"url" => "{scheme}://#{url}",
|
42
|
+
"variables" => { "scheme" => { "enum" => Array(enum) } }
|
43
|
+
}]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class OpenAPI::Loader::Translator
|
2
|
+
#
|
3
|
+
# Denormalizes 'consumes' setting for requestBody
|
4
|
+
#
|
5
|
+
# @private
|
6
|
+
#
|
7
|
+
class DenormalizeConsumes < SimpleDelegator
|
8
|
+
def call
|
9
|
+
paths.each do |path|
|
10
|
+
path_consumes = path.delete("consumes") || root_consumes
|
11
|
+
operations(path).each do |operation|
|
12
|
+
consumes = operation.delete("consumes") || path_consumes
|
13
|
+
operation["consumes"] = consumes if consumes?(operation)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def root_consumes
|
21
|
+
@root_consumes ||= delete("consumes")
|
22
|
+
end
|
23
|
+
|
24
|
+
def paths
|
25
|
+
Enumerator.new do |yielder|
|
26
|
+
fetch("paths", {}).each_value do |item|
|
27
|
+
yielder << item if item.is_a? Hash
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def operations(path)
|
33
|
+
Enumerator.new do |yielder|
|
34
|
+
path.each_value { |item| yielder << item if item.is_a? Hash }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def consumes?(operation)
|
39
|
+
Array(operation["parameters"]).any? do |item|
|
40
|
+
item.is_a?(Hash) && %w[body formData].include?(item["in"])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class OpenAPI::Loader::Translator
|
2
|
+
#
|
3
|
+
# Denormalizes 'parameters' of 'body' and 'formData' rigth into operations
|
4
|
+
# This is needed for building a 'requestBody' part of the schema
|
5
|
+
# from 'parameters' and 'consumes'.
|
6
|
+
#
|
7
|
+
# @private
|
8
|
+
#
|
9
|
+
class DenormalizeParameters < SimpleDelegator
|
10
|
+
def call
|
11
|
+
root_params = extract(self)
|
12
|
+
paths.each do |path|
|
13
|
+
path_params = merge root_params, extract(path)
|
14
|
+
operations(path).each do |operation|
|
15
|
+
parameters = merge path_params, params(operation)
|
16
|
+
operation["parameters"] = parameters if parameters.any?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def paths
|
24
|
+
Enumerator.new do |yielder|
|
25
|
+
fetch("paths", {}).each_value do |path|
|
26
|
+
yielder << path if path.is_a? Hash
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def operations(path)
|
32
|
+
Enumerator.new do |yielder|
|
33
|
+
path.each_value { |item| yielder << item if item.is_a? Hash }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def params(data)
|
38
|
+
items = data.delete("parameters")
|
39
|
+
return [] unless items.is_a? Array
|
40
|
+
items.select { |item| item.is_a?(Hash) && item["in"] && item["name"] }
|
41
|
+
end
|
42
|
+
|
43
|
+
def extract(data)
|
44
|
+
body, non_body = \
|
45
|
+
params(data).partition { |item| %w[body formData].include? item["in"] }
|
46
|
+
data["parameters"] = non_body if non_body.any?
|
47
|
+
body
|
48
|
+
end
|
49
|
+
|
50
|
+
def merge(left, right)
|
51
|
+
names = right.map { |item| item["name"] }
|
52
|
+
left.reject { |item| names.include? item["name"] } + right
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class OpenAPI::Loader::Translator
|
2
|
+
#
|
3
|
+
# Denormalizes 'consumes' and 'produces' setting
|
4
|
+
#
|
5
|
+
# @private
|
6
|
+
#
|
7
|
+
class DenormalizeProduces < SimpleDelegator
|
8
|
+
def call
|
9
|
+
paths.each do |path|
|
10
|
+
path_produces = path.delete("produces") || root_produces
|
11
|
+
operations(path).each do |operation|
|
12
|
+
produces = operation.delete("produces") || path_produces
|
13
|
+
responses(operation).each do |response|
|
14
|
+
response["produces"] = produces
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def root_produces
|
23
|
+
@root_produces ||= delete("produces")
|
24
|
+
end
|
25
|
+
|
26
|
+
def paths
|
27
|
+
Enumerator.new do |yielder|
|
28
|
+
fetch("paths", {}).each_value do |item|
|
29
|
+
yielder << item if item.is_a? Hash
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def operations(path)
|
35
|
+
Enumerator.new do |yielder|
|
36
|
+
path.each_value { |item| yielder << item if item.is_a? Hash }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def responses(operation)
|
41
|
+
data = operation["responses"]
|
42
|
+
if data.is_a? Hash
|
43
|
+
Enumerator.new do |yielder|
|
44
|
+
data.each_value do |response|
|
45
|
+
yielder << response if response.is_a?(Hash) && response["schema"]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
else
|
49
|
+
[]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|