open_api-loader 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +15 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +28 -0
  6. data/.travis.yml +24 -0
  7. data/CHANGELOG.md +10 -0
  8. data/Gemfile +8 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +99 -0
  11. data/Rakefile +10 -0
  12. data/bin/console +6 -0
  13. data/bin/setup +6 -0
  14. data/lib/open_api-loader.rb +1 -0
  15. data/lib/open_api/loader.rb +44 -0
  16. data/lib/open_api/loader/collector.rb +61 -0
  17. data/lib/open_api/loader/denormalizer.rb +27 -0
  18. data/lib/open_api/loader/denormalizer/parameters.rb +46 -0
  19. data/lib/open_api/loader/denormalizer/security.rb +35 -0
  20. data/lib/open_api/loader/denormalizer/servers.rb +36 -0
  21. data/lib/open_api/loader/denormalizer/variables.rb +54 -0
  22. data/lib/open_api/loader/reader.rb +47 -0
  23. data/lib/open_api/loader/ref.rb +85 -0
  24. data/lib/open_api/loader/translator.rb +50 -0
  25. data/lib/open_api/loader/translator/clean_definitions.rb +16 -0
  26. data/lib/open_api/loader/translator/convert_bodies.rb +77 -0
  27. data/lib/open_api/loader/translator/convert_forms.rb +71 -0
  28. data/lib/open_api/loader/translator/convert_parameters.rb +135 -0
  29. data/lib/open_api/loader/translator/convert_responses.rb +63 -0
  30. data/lib/open_api/loader/translator/convert_security_schemes.rb +49 -0
  31. data/lib/open_api/loader/translator/convert_servers.rb +46 -0
  32. data/lib/open_api/loader/translator/convert_version.rb +12 -0
  33. data/lib/open_api/loader/translator/denormalize_consumes.rb +44 -0
  34. data/lib/open_api/loader/translator/denormalize_parameters.rb +55 -0
  35. data/lib/open_api/loader/translator/denormalize_produces.rb +53 -0
  36. data/open_api-loader.gemspec +25 -0
  37. data/spec/fixtures/oas2/collected.yaml +1012 -0
  38. data/spec/fixtures/oas2/denormalized.yaml +1462 -0
  39. data/spec/fixtures/oas2/loaded.yaml +564 -0
  40. data/spec/fixtures/oas2/models.yaml +118 -0
  41. data/spec/fixtures/oas2/source.json +1 -0
  42. data/spec/fixtures/oas2/source.yaml +569 -0
  43. data/spec/fixtures/oas2/translated.yaml +1396 -0
  44. data/spec/fixtures/oas3/collected.yaml +233 -0
  45. data/spec/fixtures/oas3/denormalized.yaml +233 -0
  46. data/spec/fixtures/oas3/source.json +1 -0
  47. data/spec/fixtures/oas3/source.yaml +217 -0
  48. data/spec/loader_spec.rb +53 -0
  49. data/spec/spec_helper.rb +17 -0
  50. data/spec/support/fixture_helpers.rb +18 -0
  51. data/spec/support/path_helpers.rb +27 -0
  52. data/spec/unit/collector_spec.rb +31 -0
  53. data/spec/unit/denormalizer_spec.rb +17 -0
  54. data/spec/unit/reader_spec.rb +53 -0
  55. data/spec/unit/ref_spec.rb +107 -0
  56. data/spec/unit/translator_spec.rb +15 -0
  57. 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,12 @@
1
+ class OpenAPI::Loader::Translator
2
+ #
3
+ # Wraps the schema and changes version of the OAS
4
+ # @private
5
+ #
6
+ class ConvertVersion < SimpleDelegator
7
+ def call
8
+ delete "swagger"
9
+ self["openapi"] = "3.0.0"
10
+ end
11
+ end
12
+ 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