open_api-loader 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|