oas_parser_reborn 0.25.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +114 -0
- data/Rakefile +6 -0
- data/lib/oas_parser_reborn/abstract_attribute.rb +89 -0
- data/lib/oas_parser_reborn/callback.rb +20 -0
- data/lib/oas_parser_reborn/definition.rb +61 -0
- data/lib/oas_parser_reborn/endpoint.rb +137 -0
- data/lib/oas_parser_reborn/errors.rb +8 -0
- data/lib/oas_parser_reborn/parameter.rb +44 -0
- data/lib/oas_parser_reborn/parser.rb +77 -0
- data/lib/oas_parser_reborn/path.rb +45 -0
- data/lib/oas_parser_reborn/payload.rb +21 -0
- data/lib/oas_parser_reborn/pointer.rb +64 -0
- data/lib/oas_parser_reborn/property.rb +54 -0
- data/lib/oas_parser_reborn/raw_accessor.rb +26 -0
- data/lib/oas_parser_reborn/request_body.rb +61 -0
- data/lib/oas_parser_reborn/response.rb +18 -0
- data/lib/oas_parser_reborn/response_parser.rb +227 -0
- data/lib/oas_parser_reborn/version.rb +3 -0
- data/lib/oas_parser_reborn/webhook.rb +9 -0
- data/lib/oas_parser_reborn.rb +22 -0
- metadata +264 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
module OasParser
|
2
|
+
class Pointer
|
3
|
+
def initialize(raw_pointer)
|
4
|
+
@raw_pointer = raw_pointer
|
5
|
+
end
|
6
|
+
|
7
|
+
def resolve(document)
|
8
|
+
return document if escaped_pointer == ""
|
9
|
+
|
10
|
+
tokens.reduce(document) do |nested_doc, token|
|
11
|
+
nested_doc.fetch(token) unless !nested_doc.key?(token)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Detect circular reference by checking whether the ref exists in current path.
|
16
|
+
#
|
17
|
+
# Example:
|
18
|
+
# components:
|
19
|
+
# schemas:
|
20
|
+
# Pet:
|
21
|
+
# type: object
|
22
|
+
# properties:
|
23
|
+
# name:
|
24
|
+
# type: string
|
25
|
+
# children:
|
26
|
+
# type: array
|
27
|
+
# items: # <--- parsing here
|
28
|
+
# - $ref: '#/components/schemas/Pet'
|
29
|
+
#
|
30
|
+
# path: "/components/schemas/Pet/properties/children/items"
|
31
|
+
# raw_pointer: "#/components/schemas/Pet"
|
32
|
+
#
|
33
|
+
# It'd return true when we're parsing the pet children items where the ref points back to itself.
|
34
|
+
def circular_reference?(path)
|
35
|
+
path.include?("#{escaped_pointer}/")
|
36
|
+
end
|
37
|
+
|
38
|
+
def escaped_pointer
|
39
|
+
if @raw_pointer.start_with?("#")
|
40
|
+
Addressable::URI.unencode(@raw_pointer[1..-1])
|
41
|
+
else
|
42
|
+
@raw_pointer
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def parse_token(token)
|
49
|
+
token.gsub("~0", "~").gsub("~1", "/")
|
50
|
+
end
|
51
|
+
|
52
|
+
def tokens
|
53
|
+
tokens = escaped_pointer[1..-1].split("/")
|
54
|
+
|
55
|
+
if @raw_pointer.end_with?("/")
|
56
|
+
tokens << ""
|
57
|
+
end
|
58
|
+
|
59
|
+
tokens.map do |token|
|
60
|
+
parse_token(token)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module OasParser
|
2
|
+
class Property < AbstractAttribute
|
3
|
+
raw_keys :description, :type, :format, :minimum, :maximum,
|
4
|
+
:example, :default, :items, :nullable
|
5
|
+
|
6
|
+
attr_accessor :owner, :schema, :raw
|
7
|
+
attr_writer :name
|
8
|
+
|
9
|
+
def initialize(owner, schema, name, raw)
|
10
|
+
super(name)
|
11
|
+
@owner = owner
|
12
|
+
@schema = schema
|
13
|
+
@raw = raw
|
14
|
+
end
|
15
|
+
|
16
|
+
def required
|
17
|
+
return false unless schema['required']
|
18
|
+
schema['required'].include? name
|
19
|
+
end
|
20
|
+
|
21
|
+
def nullable?
|
22
|
+
return true if raw['nullable']
|
23
|
+
return false unless schema['nullable']
|
24
|
+
end
|
25
|
+
|
26
|
+
def convert_property_schema_to_properties(schema)
|
27
|
+
if schema['allOf']
|
28
|
+
schema['properties'] = {}
|
29
|
+
schema['allOf'].each do |p|
|
30
|
+
schema['properties'].deep_merge!(p['properties'])
|
31
|
+
end
|
32
|
+
schema.delete('allOf')
|
33
|
+
end
|
34
|
+
|
35
|
+
if schema['oneOf']
|
36
|
+
schema['oneOf'].map do |subschema|
|
37
|
+
subschema['properties'] = convert_property_schema_to_properties(subschema)
|
38
|
+
subschema['subschema_property'] = true
|
39
|
+
subschema
|
40
|
+
end
|
41
|
+
elsif schema['subschema_property']
|
42
|
+
schema = schema['properties'] if schema['properties']
|
43
|
+
schema.map do |definition|
|
44
|
+
OasParser::Property.new(self, raw, definition.name, definition.raw)
|
45
|
+
end
|
46
|
+
else
|
47
|
+
schema = schema['properties'] if schema['properties']
|
48
|
+
schema.map do |key, definition|
|
49
|
+
OasParser::Property.new(self, raw, key, definition)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module OasParser
|
2
|
+
module RawAccessor
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
def method_missing(method_name, *args, &block)
|
8
|
+
super unless self.class.get_raw_keys.include? method_name
|
9
|
+
raw[method_name.to_s]
|
10
|
+
end
|
11
|
+
|
12
|
+
def respond_to_missing?(method_name, include_private = false)
|
13
|
+
self.class.get_raw_keys.include?(method_name) || super
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def raw_keys(*args)
|
18
|
+
@raw_keys = args
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_raw_keys
|
22
|
+
@raw_keys || []
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module OasParser
|
2
|
+
class RequestBody < Payload
|
3
|
+
include OasParser::RawAccessor
|
4
|
+
raw_keys :description, :required, :content
|
5
|
+
|
6
|
+
attr_accessor :endpoint, :raw
|
7
|
+
|
8
|
+
def initialize(endpoint, raw)
|
9
|
+
@endpoint = endpoint
|
10
|
+
@raw = raw
|
11
|
+
end
|
12
|
+
|
13
|
+
def properties_for_format(format)
|
14
|
+
s = schema(format)
|
15
|
+
s = handle_all_of(s)
|
16
|
+
s['properties'].map do |name, definition|
|
17
|
+
OasParser::Property.new(self, s, name, definition)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def split_properties_for_format(format)
|
22
|
+
split_schemas(format).map do |schema|
|
23
|
+
schema = handle_all_of(schema)
|
24
|
+
schema['properties'].map do |name, definition|
|
25
|
+
OasParser::Property.new(self, schema, name, definition)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def handle_all_of(schema)
|
31
|
+
newSchema = {}
|
32
|
+
if schema['allOf']
|
33
|
+
schema['allOf'].each do |p|
|
34
|
+
newSchema = deep_safe_merge(newSchema, p)
|
35
|
+
if newSchema['allOf']
|
36
|
+
newSchema = deep_safe_merge(newSchema, handle_all_of(newSchema))
|
37
|
+
newSchema.delete('allOf')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
else
|
41
|
+
newSchema = schema
|
42
|
+
end
|
43
|
+
|
44
|
+
newSchema
|
45
|
+
end
|
46
|
+
|
47
|
+
def deep_safe_merge(source_hash, new_hash)
|
48
|
+
source_hash.merge(new_hash) do |key, old, new|
|
49
|
+
if new.respond_to?(:blank) && new.blank?
|
50
|
+
old
|
51
|
+
elsif (old.kind_of?(Hash) and new.kind_of?(Hash))
|
52
|
+
old.deep_merge(new)
|
53
|
+
elsif (old.kind_of?(Array) and new.kind_of?(Array))
|
54
|
+
old.concat(new).uniq
|
55
|
+
else
|
56
|
+
new
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module OasParser
|
2
|
+
class Response < Payload
|
3
|
+
include OasParser::RawAccessor
|
4
|
+
raw_keys :description, :content
|
5
|
+
|
6
|
+
attr_accessor :endpoint, :code, :raw
|
7
|
+
|
8
|
+
def initialize(endpoint, code, raw)
|
9
|
+
@endpoint = endpoint
|
10
|
+
@code = code
|
11
|
+
@raw = raw
|
12
|
+
end
|
13
|
+
|
14
|
+
def success?
|
15
|
+
code.match?(/^2\d\d$/)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
module OasParser
|
2
|
+
class ResponseParser
|
3
|
+
attr_accessor :raw
|
4
|
+
|
5
|
+
def initialize(raw)
|
6
|
+
@raw = raw
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse(mode = nil)
|
10
|
+
@mode = mode
|
11
|
+
route(@raw)
|
12
|
+
end
|
13
|
+
|
14
|
+
def json
|
15
|
+
parse('json').to_json
|
16
|
+
end
|
17
|
+
|
18
|
+
def xml(xml_options = {})
|
19
|
+
xml_options ||= {}
|
20
|
+
xml_options = default_xml_options.merge(xml_options)
|
21
|
+
|
22
|
+
raw_xml = parse('xml').to_xml(xml_options)
|
23
|
+
|
24
|
+
xml_document = Nokogiri::XML(raw_xml)
|
25
|
+
|
26
|
+
xml_document.xpath('//__attributes').each do |attributes|
|
27
|
+
attributes.children.each do |attribute|
|
28
|
+
next unless attribute.class == Nokogiri::XML::Element
|
29
|
+
attribute.parent.parent.css("> #{attribute.name}").remove
|
30
|
+
attribute.parent.parent[attribute.name] = attribute.content
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
xml_document.xpath('//__array_attributes').each do |attributes|
|
35
|
+
attributes.children.each do |attribute|
|
36
|
+
next unless attribute.class == Nokogiri::XML::Element
|
37
|
+
|
38
|
+
parameter = {}
|
39
|
+
parameter['example'] = attribute.css('example').text if attribute.css('example')
|
40
|
+
parameter['type'] = attribute.css('type').text if attribute.css('type')
|
41
|
+
|
42
|
+
attribute.parent.parent.parent[attribute.name] = parameter_value(parameter)
|
43
|
+
end
|
44
|
+
attributes.parent.remove
|
45
|
+
end
|
46
|
+
|
47
|
+
xml_document.xpath('//__text').each do |attribute|
|
48
|
+
value = attribute.children.last.content
|
49
|
+
attribute.parent.content = value
|
50
|
+
end
|
51
|
+
|
52
|
+
xml_document.xpath('//__attributes').each(&:remove)
|
53
|
+
|
54
|
+
xml_document.to_xml.each_line.reject { |x| x.strip == '' }.join
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def default_xml_options
|
60
|
+
{
|
61
|
+
dasherize: false,
|
62
|
+
skip_types: true,
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def route(root_object)
|
67
|
+
case root_object['type']
|
68
|
+
when 'object'
|
69
|
+
parse_object(root_object)
|
70
|
+
when 'array' then parse_array(root_object)
|
71
|
+
when 'string' then root_object['example']
|
72
|
+
when 'integer' then root_object['example']
|
73
|
+
when 'number' then root_object['example']
|
74
|
+
when nil
|
75
|
+
return nil if root_object['additionalProperties'] == false
|
76
|
+
return nil if root_object['properties'] == {}
|
77
|
+
|
78
|
+
if treat_as_object?(root_object)
|
79
|
+
# Handle objects with missing type
|
80
|
+
return parse_object(root_object.merge({ 'type' => 'object' }))
|
81
|
+
end
|
82
|
+
|
83
|
+
raise StandardError.new("Unhandled object #{root_object} with missing type")
|
84
|
+
else
|
85
|
+
raise StandardError.new("Don't know how to parse #{root_object['type']}")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_object(object)
|
90
|
+
raise StandardError.new("Not a hash") unless object.class == Hash
|
91
|
+
raise StandardError.new("Not an object") unless treat_as_object?(object)
|
92
|
+
|
93
|
+
if object['allOf']
|
94
|
+
merged_object = { 'type' => 'object' }
|
95
|
+
object['allOf'].each { |o| merged_object.deep_merge!(o) }
|
96
|
+
return parameter_value(merged_object)
|
97
|
+
elsif object['properties']
|
98
|
+
o = {}
|
99
|
+
object['properties'].each do |key, value|
|
100
|
+
if @mode == 'xml'
|
101
|
+
if is_xml_attribute?(value)
|
102
|
+
o['__attributes'] ||= {}
|
103
|
+
o['__attributes'][key] = parameter_value(value)
|
104
|
+
end
|
105
|
+
|
106
|
+
if is_xml_text?(value)
|
107
|
+
o['__text'] = parameter_value(value)
|
108
|
+
end
|
109
|
+
|
110
|
+
if has_xml_name?(value)
|
111
|
+
key = xml_name(value)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
o[key] = parameter_value(value)
|
116
|
+
end
|
117
|
+
|
118
|
+
return o
|
119
|
+
end
|
120
|
+
|
121
|
+
{}
|
122
|
+
end
|
123
|
+
|
124
|
+
def parse_array(object)
|
125
|
+
raise StandardError.new("Not an array") unless object['type'] == 'array'
|
126
|
+
|
127
|
+
attributes = {}
|
128
|
+
|
129
|
+
if object['properties']
|
130
|
+
if @mode == 'xml'
|
131
|
+
object['properties'].each do |key, value|
|
132
|
+
if is_xml_attribute?(value)
|
133
|
+
attributes[key] = value
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
if object['items'] && (object['items']['oneOf'] || object['items']['anyOf'])
|
140
|
+
key = object['items']['oneOf'] ? 'oneOf' : 'anyOf'
|
141
|
+
items = object['items'][key].map do |obj|
|
142
|
+
route(obj)
|
143
|
+
end
|
144
|
+
|
145
|
+
items.push({ '__array_attributes' => attributes }) if attributes.any? && @mode == 'xml'
|
146
|
+
return items
|
147
|
+
end
|
148
|
+
|
149
|
+
case object['items']['type']
|
150
|
+
when 'object'
|
151
|
+
if attributes.any? && @mode == 'xml'
|
152
|
+
[parse_object(object['items']), { '__array_attributes' => attributes }]
|
153
|
+
else
|
154
|
+
[parse_object(object['items'])]
|
155
|
+
end
|
156
|
+
else
|
157
|
+
if object['items']
|
158
|
+
# Handle objects with missing type
|
159
|
+
object['items']['type'] = 'object'
|
160
|
+
if @mode == 'xml'
|
161
|
+
[parse_object(object['items']), { '__array_attributes' => attributes }]
|
162
|
+
else
|
163
|
+
[parse_object(object['items'])]
|
164
|
+
end
|
165
|
+
else
|
166
|
+
raise StandardError.new("parse_array: Don't know how to parse object")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def parameter_value(parameter)
|
172
|
+
return parameter['example'] if parameter['example']
|
173
|
+
case (parameter['schema'] ? parameter['schema']['type'] : parameter['type'])
|
174
|
+
when 'integer' then return 1
|
175
|
+
when 'number' then return 1.0
|
176
|
+
when 'string' then return 'abc123'
|
177
|
+
when 'boolean' then return false
|
178
|
+
when 'object' then return parse_object(parameter)
|
179
|
+
when 'array' then return parse_array(parameter)
|
180
|
+
else
|
181
|
+
if treat_as_object?(parameter)
|
182
|
+
return parse_object(parameter)
|
183
|
+
end
|
184
|
+
|
185
|
+
if parameter['type']
|
186
|
+
raise StandardError.new("Can not resolve parameter type of #{parameter['type']}")
|
187
|
+
else
|
188
|
+
raise StandardError.new("Parameter #{parameter} is missing type.")
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
def treat_as_object?(object)
|
195
|
+
return true if object['type'] == 'object'
|
196
|
+
return true if object['allOf']
|
197
|
+
return true if object['oneOf']
|
198
|
+
return true if object['properties']
|
199
|
+
false
|
200
|
+
end
|
201
|
+
|
202
|
+
def has_xml_options?(object)
|
203
|
+
object['xml'].present?
|
204
|
+
end
|
205
|
+
|
206
|
+
def is_xml_attribute?(object)
|
207
|
+
return false unless has_xml_options?(object)
|
208
|
+
object['xml']['attribute'] || false
|
209
|
+
end
|
210
|
+
|
211
|
+
def is_xml_text?(object)
|
212
|
+
# See: https://github.com/OAI/OpenAPI-Specification/issues/630#issuecomment-350680346
|
213
|
+
return false unless has_xml_options?(object)
|
214
|
+
return true if object['xml']['text'] || false
|
215
|
+
object['xml']['x-text'] || false
|
216
|
+
end
|
217
|
+
|
218
|
+
def has_xml_name?(object)
|
219
|
+
return false unless has_xml_options?(object)
|
220
|
+
xml_name(object) || false
|
221
|
+
end
|
222
|
+
|
223
|
+
def xml_name(object)
|
224
|
+
object['xml']['name']
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'uri'
|
3
|
+
require 'yaml'
|
4
|
+
require 'active_support/core_ext/object/blank'
|
5
|
+
require 'active_support/core_ext/hash/conversions'
|
6
|
+
require 'nokogiri'
|
7
|
+
|
8
|
+
require 'addressable/uri'
|
9
|
+
require 'deep_merge'
|
10
|
+
|
11
|
+
require_relative 'oas_parser_reborn/path.rb'
|
12
|
+
require_relative 'oas_parser_reborn/payload.rb'
|
13
|
+
require_relative 'oas_parser_reborn/raw_accessor.rb'
|
14
|
+
require_relative 'oas_parser_reborn/abstract_attribute.rb'
|
15
|
+
|
16
|
+
Dir["#{File.dirname(__FILE__)}/oas_parser_reborn/**/*.rb"].each do |file|
|
17
|
+
require file
|
18
|
+
end
|
19
|
+
|
20
|
+
module OasParser
|
21
|
+
# Your code goes here...
|
22
|
+
end
|