oas_parser_reborn 0.25.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,3 @@
1
+ module OasParser
2
+ VERSION = '0.25.5'.freeze
3
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasParser
4
+ class Webhook < Path
5
+ def name
6
+ path
7
+ end
8
+ end
9
+ 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