oas_parser_reborn 0.25.5

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.
@@ -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