scorpio 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ef095c64774f95040582a9945d63cb2e58685403b66d49a9df1ec8976c5d12b
4
- data.tar.gz: 777af82e020ab367e4280f10995b0aa84648782a157bc925529132c79e0b57d6
3
+ metadata.gz: b089f79e383e48c4d73793ceb0b1f3a3a254aca6abc34176252d24828ca0a9d8
4
+ data.tar.gz: aa9f34563eacae7e07c68f757a66bc53504dbdde741c285f40b34f15a64a08a4
5
5
  SHA512:
6
- metadata.gz: 10fc95d393931ac8931c16bbe12dd62366e73480dc7ac5fa7c38efe8ab21cf49506eb659b0734bc3848df1a4994795adb6eb15302c3e3d942051ebe74f92dc09
7
- data.tar.gz: e1ebe65228ca3412b24fedf99d0fbf84cd46acea560d09b32fc54a6142308fbfa088b471e65a0128b0ebffa1b1b4f5a3143be8c7f5b823a8c19c1d02eaaf5054
6
+ metadata.gz: 3d87134df5c11b0157c861fddd25947d3f45cbd2b17c29d9de23d22e8dd70e4edc2b6b1378ef3f5cfee0e0bff7872e9179e76b5f97e824d14a231ef19fd068cd
7
+ data.tar.gz: f82dd97cdb8a8b2f0912d1d5e9a8198f5f7734dcb46c914b0c9cb25a3763d0c0a9d4c0c690a3598d53a8c62f065fde18dec0c42586f2257da289b776d091a3c2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # v0.6.0
2
+ - ResourceBase resources in responses may be contained anywhere in the response object contained in ResourceBase::Container
3
+ - ResourceBase defines class methods for operations whose request or response schemas are represented by the resource
4
+ - JSI v0.6.0
5
+
1
6
  # v0.5.0
2
7
  - JSI schema classes change to schema modules, JSI instances may have multiple schemas (JSI v0.4, Ur v0.2)
3
8
 
data/LICENSE.md CHANGED
@@ -2,7 +2,7 @@ Copright © [Ethan](https://github.com/notEthan/)
2
2
 
3
3
  [<img align="right" src="https://github.com/notEthan/scorpio/raw/master/resources/icons/AGPL-3.0.png">](https://www.gnu.org/licenses/agpl-3.0.html)
4
4
 
5
- Scorpio is Open Source Software licensed under the terms of the [GNU Affero General Public License version 3](https://www.gnu.org/licenses/agpl-3.0.html).
5
+ Scorpio is licensed under the terms of the [GNU Affero General Public License version 3](https://www.gnu.org/licenses/agpl-3.0.html).
6
6
 
7
7
  GNU Affero General Public License
8
8
  =================================
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Scorpio
2
2
 
3
- [![Build Status](https://travis-ci.org/notEthan/scorpio.svg?branch=master)](https://travis-ci.org/notEthan/scorpio)
3
+ ![Test CI Status](https://github.com/notEthan/scorpio/actions/workflows/test.yml/badge.svg?branch=stable)
4
4
  [![Coverage Status](https://coveralls.io/repos/github/notEthan/scorpio/badge.svg)](https://coveralls.io/github/notEthan/scorpio)
5
5
 
6
6
  Scorpio is a library that helps you, as a client, consume an HTTP service described by an OpenAPI document. You provide the OpenAPI description document, a little bit of configuration, and Scorpio will take that and dynamically generate an interface for you to call the service's operations and interact with its resources as an ORM.
@@ -24,7 +24,7 @@ Once you have the OpenAPI document describing the service you will consume, you
24
24
 
25
25
  ## Pet Store (using Scorpio::ResourceBase)
26
26
 
27
- Let's dive into some code, shall we? If you have learned about OpenAPI, you likely learned using the example of the Pet Store service. This README will use the same service. Its documentation is at http://petstore.swagger.io/.
27
+ Let's dive into some code, shall we? If you have learned about OpenAPI, you likely learned using the example of the Pet Store service. This README will use the same service. Its documentation is at https://petstore.swagger.io/.
28
28
 
29
29
  Using the OpenAPI document, we can start interacting with the pet store with very little code. Here is that code, with explanations of each part in the comments.
30
30
 
@@ -42,7 +42,7 @@ module PetStore
42
42
  # (making network calls at application boot time is usually a bad idea), but for this
43
43
  # example we will do a quick-and-dirty http get.
44
44
  require 'json'
45
- self.openapi_document = JSON.parse(Faraday.get('http://petstore.swagger.io/v2/swagger.json').body)
45
+ self.openapi_document = JSON.parse(Faraday.get('https://petstore.swagger.io/v2/swagger.json').body)
46
46
  end
47
47
 
48
48
  # a Pet is a resource of the pet store, so inherits from PetStore::Resource
@@ -67,7 +67,8 @@ end
67
67
  That should be all you need to start calling operations:
68
68
 
69
69
  ```ruby
70
- # call the operation findPetsByStatus: http://petstore.swagger.io/#/pet/findPetsByStatus
70
+ # call the operation findPetsByStatus
71
+ # doc: https://petstore.swagger.io/#/pet/findPetsByStatus
71
72
  sold_pets = PetStore::Pet.findPetsByStatus(status: 'sold')
72
73
  # sold_pets is an array-like collection of PetStore::Pet instances
73
74
 
@@ -79,7 +80,7 @@ pet.tags.map(&:name)
79
80
  # (your tag names will be different depending on what's in the pet store)
80
81
  # => ["aucune"]
81
82
 
82
- # compare to getPetById: http://petstore.swagger.io/#/pet/getPetById
83
+ # compare to getPetById: https://petstore.swagger.io/#/pet/getPetById
83
84
  pet == PetStore::Pet.getPetById(petId: pet['id'])
84
85
  # pet is the same, retrieved using the getPetById operation
85
86
 
@@ -89,7 +90,7 @@ pet.name = ENV['USER']
89
90
  # store the result in the pet store. note the updatePet call from the instance - our
90
91
  # calls so far have been on the class PetStore::Pet, but scorpio defines instance
91
92
  # methods to call operations where appropriate as well.
92
- # updatePet: http://petstore.swagger.io/#/pet/updatePet
93
+ # updatePet: https://petstore.swagger.io/#/pet/updatePet
93
94
  pet.updatePet
94
95
 
95
96
  # check that it was saved
@@ -107,13 +108,13 @@ Isn't that cool? You get class methods like getPetById, instance methods like up
107
108
 
108
109
  ## Pet Store (using Scorpio::OpenAPI classes)
109
110
 
110
- You do not have to define resource classes to use Scorpio to call OpenAPI operations - the classes Scorpio uses to represent concepts from OpenAPI can be called directly. Scorpio uses [JSI](https://github.com/notEthan/ur) classes to represent OpenAPI schemes such as the Document and its Operations.
111
+ You do not have to define resource classes to use Scorpio to call OpenAPI operations - the classes Scorpio uses to represent concepts from OpenAPI can be called directly. Scorpio uses [JSI](https://github.com/notEthan/jsi) classes to represent OpenAPI schemes such as the Document and its Operations.
111
112
 
112
113
  We start by instantiating the OpenAPI document. `Scorpio::OpenAPI::Document.from_instance` returns a V2 or V3 OpenAPI Document class instance.
113
114
 
114
115
  ```ruby
115
116
  require 'scorpio'
116
- pet_store_doc = Scorpio::OpenAPI::Document.from_instance(JSON.parse(Faraday.get('http://petstore.swagger.io/v2/swagger.json').body))
117
+ pet_store_doc = Scorpio::OpenAPI::Document.from_instance(JSON.parse(Faraday.get('https://petstore.swagger.io/v2/swagger.json').body))
117
118
  # => #{<Scorpio::OpenAPI::V2::Document fragment="#"> "swagger" => "2.0", ...}
118
119
  ```
119
120
 
@@ -122,7 +123,7 @@ The OpenAPI document holds the JSON that represents it, so to get an Operation y
122
123
  ```ruby
123
124
  # the store inventory operation will let us see what statuses there are in the store.
124
125
  inventory_op = pet_store_doc.paths['/store/inventory']['get']
125
- # => #{<Scorpio::OpenAPI::V2::Operation fragment="#/paths/~1store~1inventory/get">
126
+ # => #{<JSI (Scorpio::OpenAPI::V2::Operation)>
126
127
  # "summary" => "Returns pet inventories by status",
127
128
  # "operationId" => "getInventory",
128
129
  # ...
@@ -140,7 +141,7 @@ Now that we have an operation, we can run requests from it. {Scorpio::OpenAPI::O
140
141
 
141
142
  ```ruby
142
143
  inventory = inventory_op.run
143
- # => #{<JSI::SchemaClasses["dde3#/paths/~1store~1inventory/get/responses/200/schema"] fragment="#">
144
+ # => #{<JSI>
144
145
  # "unavailable" => 4,
145
146
  # "unloved - needs a home" => 1,
146
147
  # "available" => 2350,
@@ -152,28 +153,36 @@ inventory = inventory_op.run
152
153
  let's pick a state and find a pet. we'll go through the rest of the example in the ResourceBase section pretty much like it is up there:
153
154
 
154
155
  ```ruby
155
- # call the operation findPetsByStatus: http://petstore.swagger.io/#/pet/findPetsByStatus
156
+ # call the operation findPetsByStatus
157
+ # doc: https://petstore.swagger.io/#/pet/findPetsByStatus
156
158
  sold_pets = pet_store_doc.operations['findPetsByStatus'].run(status: 'sold')
157
159
  # sold_pets is an array-like collection of JSI instances
158
160
 
159
161
  pet = sold_pets.detect { |pet| pet.tags.any? }
160
162
 
161
163
  pet.tags.map(&:name)
162
- # note that you have accessors on PetStore::Pet like #tags, and also that
164
+ # note that you have accessors on the returned JSI like #tags, and also that
163
165
  # tags have accessors for properties 'name' and 'id' from the tags schema
164
166
  # (your tag names will be different depending on what's in the pet store)
165
167
  # => ["aucune"]
166
168
 
167
- # compare to getPetById: http://petstore.swagger.io/#/pet/getPetById
168
- pet == pet_store_doc.operations['getPetById'].run(petId: pet['id'])
169
+ # compare the pet from findPetsByStatus to one returned from getPetById
170
+ # doc: https://petstore.swagger.io/#/pet/getPetById
171
+ pet_by_id = pet_store_doc.operations['getPetById'].run(petId: pet['id'])
172
+
173
+ # unlike ResourceBase instances above, JSI instances have stricter
174
+ # equality and the pets returned from different operations are not
175
+ # equal, though the underlying JSON instance is.
176
+ pet_by_id == pet
169
177
  # => false
170
- # without ResourceBase, pet is not considered to be the same compared with getPetById [TODO may change in jsi]
178
+ pet_by_id.jsi_instance == pet.jsi_instance
179
+ # => true
171
180
 
172
181
  # let's name the pet after ourself
173
182
  pet.name = ENV['USER']
174
183
 
175
184
  # store the result in the pet store.
176
- # updatePet: http://petstore.swagger.io/#/pet/updatePet
185
+ # updatePet: https://petstore.swagger.io/#/pet/updatePet
177
186
  pet_store_doc.operations['updatePet'].run(body_object: pet)
178
187
 
179
188
  # check that it was saved
@@ -218,7 +227,7 @@ When these are set, Scorpio::ResourceBase looks through the API description and
218
227
  If you need a more complete representation of the HTTP request and/or response, Scorpio::OpenAPI::Operation#run_ur or Scorpio::Request#run_ur will return a representation of the request and response defined by the gem [Ur](https://github.com/notEthan/ur). See that link for more detail. Relating to the example above titled "Pet Store (using Scorpio::OpenAPI classes)", this code will return an Ur:
219
228
 
220
229
  ```ruby
221
- inventory_op = Scorpio::OpenAPI::Document.from_instance(JSON.parse(Faraday.get('http://petstore.swagger.io/v2/swagger.json').body)).paths['/store/inventory']['get']
230
+ inventory_op = Scorpio::OpenAPI::Document.from_instance(JSON.parse(Faraday.get('https://petstore.swagger.io/v2/swagger.json').body)).paths['/store/inventory']['get']
222
231
  inventory_ur = inventory_op.run_ur
223
232
  # => #{<Scorpio::Ur fragment="#"> ...}
224
233
  ```
@@ -1,7 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Scorpio
2
4
  module Google
3
5
  discovery_rest_description_doc = ::JSON.parse(Scorpio.root.join('documents/www.googleapis.com/discovery/v1/apis/discovery/v1/rest').read)
4
- discovery_rest_description = JSI::MetaschemaNode.new(discovery_rest_description_doc, metaschema_root_ptr: JSI::JSON::Pointer['schemas']['JsonSchema'], root_schema_ptr: JSI::JSON::Pointer['schemas']['RestDescription'])
6
+ discovery_rest_description = JSI::MetaschemaNode.new(
7
+ discovery_rest_description_doc,
8
+ metaschema_root_ptr: JSI::Ptr['schemas']['JsonSchema'],
9
+ root_schema_ptr: JSI::Ptr['schemas']['RestDescription'],
10
+ metaschema_instance_modules: [JSI::Schema::Draft04],
11
+ )
5
12
 
6
13
  # naming these is not strictly necessary, but is nice to have.
7
14
  DirectoryList = discovery_rest_description.schemas['DirectoryList'].jsi_schema_module
@@ -165,6 +172,7 @@ module Scorpio
165
172
  'schemes' => ad.rootUrl ? [Addressable::URI.parse(ad.rootUrl).scheme] : ad.baseUrl ? [Addressable::URI.parse(ad.rootUrl).scheme] : [], #/definitions/schemesList
166
173
  'consumes' => ['application/json'], # we'll just make this assumption
167
174
  'produces' => ['application/json'],
175
+ 'tags' => paths.flat_map { |_, p| p.flat_map { |_, op| (op['tags'] || []).map { |n| {'name' => n} } } }.uniq,
168
176
  'paths' => paths, #/definitions/paths
169
177
  }
170
178
  if ad.schemas
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Scorpio
2
4
  module OpenAPI
3
5
  # A document that defines or describes an API.
@@ -52,7 +54,7 @@ module Scorpio
52
54
  attr_writer :faraday_adapter
53
55
  def faraday_adapter
54
56
  return @faraday_adapter if instance_variable_defined?(:@faraday_adapter)
55
- [Faraday.default_adapter]
57
+ [Faraday.default_adapter].freeze
56
58
  end
57
59
 
58
60
  attr_writer :logger
@@ -157,7 +159,7 @@ module Scorpio
157
159
  scheme: scheme,
158
160
  host: host,
159
161
  path: basePath,
160
- ).to_s
162
+ ).freeze
161
163
  end
162
164
  end
163
165
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Scorpio
2
4
  module OpenAPI
3
5
  # An OpenAPI operation
@@ -43,37 +45,42 @@ module Scorpio
43
45
  end
44
46
  include Configurables
45
47
 
46
- # @return [Boolean] v3?
48
+ # openapi v3?
49
+ # @return [Boolean]
47
50
  def v3?
48
51
  is_a?(V3::Operation)
49
52
  end
50
53
 
51
- # @return [Boolean] v2?
54
+ # openapi v2?
55
+ # @return [Boolean]
52
56
  def v2?
53
57
  is_a?(V2::Operation)
54
58
  end
55
59
 
56
- # @return [Scorpio::OpenAPI::Document] the document whence this operation came
60
+ # the document whence this operation came
61
+ # @return [Scorpio::OpenAPI::Document]
57
62
  def openapi_document
58
- parent_jsis.detect { |p| p.is_a?(Scorpio::OpenAPI::Document) }
63
+ jsi_parent_nodes.detect { |p| p.is_a?(Scorpio::OpenAPI::Document) }
59
64
  end
60
65
 
66
+ # @return [String]
61
67
  def path_template_str
62
68
  return @path_template_str if instance_variable_defined?(:@path_template_str)
63
- raise(Bug) unless parent_jsi.is_a?(Scorpio::OpenAPI::V2::PathItem) || parent_jsi.is_a?(Scorpio::OpenAPI::V3::PathItem)
64
- raise(Bug) unless parent_jsi.parent_jsi.is_a?(Scorpio::OpenAPI::V2::Paths) || parent_jsi.parent_jsi.is_a?(Scorpio::OpenAPI::V3::Paths)
65
- @path_template_str = parent_jsi.jsi_ptr.reference_tokens.last
69
+ raise(Bug) unless jsi_parent_node.is_a?(Scorpio::OpenAPI::V2::PathItem) || jsi_parent_node.is_a?(Scorpio::OpenAPI::V3::PathItem)
70
+ raise(Bug) unless jsi_parent_node.jsi_parent_node.is_a?(Scorpio::OpenAPI::V2::Paths) || jsi_parent_node.jsi_parent_node.is_a?(Scorpio::OpenAPI::V3::Paths)
71
+ @path_template_str = jsi_parent_node.jsi_ptr.tokens.last
66
72
  end
67
73
 
68
- # @return [Addressable::Template] the path as an Addressable::Template
74
+ # the path as an Addressable::Template
75
+ # @return [Addressable::Template]
69
76
  def path_template
70
77
  return @path_template if instance_variable_defined?(:@path_template)
71
78
  @path_template = Addressable::Template.new(path_template_str)
72
79
  end
73
80
 
81
+ # the URI template, consisting of the base_url concatenated with the path template
74
82
  # @param base_url [#to_str] the base URL to which the path template is appended
75
- # @return [Addressable::Template] the URI template, consisting of the base_url
76
- # concatenated with the path template
83
+ # @return [Addressable::Template]
77
84
  def uri_template(base_url: self.base_url)
78
85
  unless base_url
79
86
  raise(ArgumentError, "no base_url has been specified for operation #{self}")
@@ -83,33 +90,38 @@ module Scorpio
83
90
  Addressable::Template.new(File.join(base_url, path_template_str))
84
91
  end
85
92
 
86
- # @return the HTTP method of this operation as indicated by the attribute name
87
- # for this operation from the parent PathItem
93
+ # the HTTP method of this operation as indicated by the attribute name for this operation
94
+ # from the parent PathItem
95
+ # @return [String]
88
96
  def http_method
89
97
  return @http_method if instance_variable_defined?(:@http_method)
90
- raise(Bug) unless parent_jsi.is_a?(Scorpio::OpenAPI::V2::PathItem) || parent_jsi.is_a?(Scorpio::OpenAPI::V3::PathItem)
91
- @http_method = jsi_ptr.reference_tokens.last
98
+ raise(Bug) unless jsi_parent_node.is_a?(Scorpio::OpenAPI::V2::PathItem) || jsi_parent_node.is_a?(Scorpio::OpenAPI::V3::PathItem)
99
+ @http_method = jsi_ptr.tokens.last
92
100
  end
93
101
 
94
- # @return [String] a short identifier for this operation appropriate for an error message
102
+ # a short identifier for this operation appropriate for an error message
103
+ # @return [String]
95
104
  def human_id
96
105
  operationId || "path: #{path_template_str}, method: #{http_method}"
97
106
  end
98
107
 
108
+ # @param status [String, Integer]
99
109
  # @return [Scorpio::OpenAPI::V3::Response, Scorpio::OpenAPI::V2::Response]
100
110
  def oa_response(status: )
101
111
  status = status.to_s if status.is_a?(Numeric)
102
- if self.responses
103
- _, oa_response = self.responses.detect { |k, v| k.to_s == status }
104
- oa_response ||= self.responses['default']
112
+ if responses
113
+ _, oa_response = responses.detect { |k, v| k.to_s == status }
114
+ oa_response ||= responses['default']
105
115
  end
106
116
  oa_response
107
117
  end
108
118
 
119
+ # the parameters specified for this operation, plus any others scorpio considers to be parameters.
120
+ #
109
121
  # this method is not intended to be API-stable at the moment.
110
122
  #
111
- # @return [#to_ary<#to_h>] the parameters specified for this operation, plus any others
112
- # scorpio considers to be parameters
123
+ # @api private
124
+ # @return [#to_ary<#to_h>]
113
125
  def inferred_parameters
114
126
  parameters = self.parameters ? self.parameters.to_a.dup : []
115
127
  path_template.variables.each do |var|
@@ -127,7 +139,8 @@ module Scorpio
127
139
  parameters
128
140
  end
129
141
 
130
- # @return [Module] a module with accessor methods for unambiguously named parameters of this operation.
142
+ # a module with accessor methods for unambiguously named parameters of this operation.
143
+ # @return [Module]
131
144
  def request_accessor_module
132
145
  return @request_accessor_module if instance_variable_defined?(:@request_accessor_module)
133
146
  @request_accessor_module = begin
@@ -136,7 +149,7 @@ module Scorpio
136
149
  instance_method_modules = [Request, Request::Configurables]
137
150
  instance_method_names = instance_method_modules.map do |mod|
138
151
  (mod.instance_methods + mod.private_instance_methods).map(&:to_s)
139
- end.inject(Set.new, &:|)
152
+ end.inject(Set.new, &:merge)
140
153
  params_by_name.each do |name, params|
141
154
  next if instance_method_names.include?(name)
142
155
  if params.size == 1
@@ -149,19 +162,22 @@ module Scorpio
149
162
  end
150
163
  end
151
164
 
152
- # @param a, b are passed to Scorpio::Request#initialize
165
+ # instantiates a {Scorpio::Request} for this operation.
166
+ # parameters are all passed to {Scorpio::Request#initialize}.
153
167
  # @return [Scorpio::Request]
154
168
  def build_request(*a, &b)
155
169
  Scorpio::Request.new(self, *a, &b)
156
170
  end
157
171
 
158
- # @param a, b are passed to Scorpio::Request#initialize
172
+ # runs a {Scorpio::Request} for this operation, returning a {Scorpio::Ur}.
173
+ # parameters are all passed to {Scorpio::Request#initialize}.
159
174
  # @return [Scorpio::Ur] response ur
160
175
  def run_ur(*a, &b)
161
176
  build_request(*a, &b).run_ur
162
177
  end
163
178
 
164
- # @param a, b are passed to Scorpio::Request#initialize
179
+ # runs a {Scorpio::Request} for this operation - see {Scorpio::Request#run}.
180
+ # parameters are all passed to {Scorpio::Request#initialize}.
165
181
  # @return response body object
166
182
  def run(*a, &b)
167
183
  build_request(*a, &b).run
@@ -212,17 +228,19 @@ module Scorpio
212
228
  requestBody['content'] &&
213
229
  requestBody['content'][media_type] &&
214
230
  requestBody['content'][media_type]['schema']
215
- schema_object ? JSI::Schema.from_object(schema_object) : nil
231
+ schema_object ? JSI::Schema.ensure_schema(schema_object) : nil
216
232
  end
217
233
 
218
- # @return [Array<JSI::Schema>]
234
+ # @return [JSI::SchemaSet]
219
235
  def request_schemas
220
- if requestBody && requestBody['content']
221
- # oamt is for Scorpio::OpenAPI::V3::MediaType
222
- oamts = requestBody['content'].values.select { |oamt| oamt.key?('schema') }
223
- oamts.map { |oamt| JSI::Schema.from_object(oamt['schema']) }
224
- else
225
- []
236
+ JSI::SchemaSet.build do |schemas|
237
+ if requestBody && requestBody['content']
238
+ requestBody['content'].each_value do |oa_media_type|
239
+ if oa_media_type['schema']
240
+ schemas << oa_media_type['schema']
241
+ end
242
+ end
243
+ end
226
244
  end
227
245
  end
228
246
 
@@ -232,7 +250,24 @@ module Scorpio
232
250
  oa_media_types = oa_response ? oa_response['content'] : nil # Scorpio::OpenAPI::V3::MediaTypes
233
251
  oa_media_type = oa_media_types ? oa_media_types[media_type] : nil # Scorpio::OpenAPI::V3::MediaType
234
252
  oa_schema = oa_media_type ? oa_media_type['schema'] : nil # Scorpio::OpenAPI::V3::Schema
235
- oa_schema ? JSI::Schema.new(oa_schema) : nil
253
+ oa_schema ? JSI::Schema.ensure_schema(oa_schema) : nil
254
+ end
255
+
256
+ # @return [JSI::SchemaSet]
257
+ def response_schemas
258
+ JSI::SchemaSet.build do |schemas|
259
+ if responses
260
+ responses.each_value do |oa_response|
261
+ if oa_response['content']
262
+ oa_response['content'].each_value do |oa_media_type|
263
+ if oa_media_type['schema']
264
+ schemas << oa_media_type['schema']
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end
236
271
  end
237
272
  end
238
273
  end
@@ -264,7 +299,8 @@ module Scorpio
264
299
  end
265
300
  include Configurables
266
301
 
267
- # @return [#to_hash] the body parameter
302
+ # the body parameter
303
+ # @return [#to_hash]
268
304
  # @raise [Scorpio::OpenAPI::SemanticError] if there's more than one body param
269
305
  def body_parameter
270
306
  body_parameters = (parameters || []).select { |parameter| parameter['in'] == 'body' }
@@ -278,19 +314,20 @@ module Scorpio
278
314
  end
279
315
  end
280
316
 
317
+ # request schema for the given media_type
281
318
  # @param media_type unused
282
- # @return [JSI::Schema] request schema for the given media_type
319
+ # @return [JSI::Schema]
283
320
  def request_schema(media_type: nil)
284
321
  if body_parameter && body_parameter['schema']
285
- JSI::Schema.new(body_parameter['schema'])
322
+ JSI::Schema.ensure_schema(body_parameter['schema'])
286
323
  else
287
324
  nil
288
325
  end
289
326
  end
290
327
 
291
- # @return [Array<JSI::Schema>]
328
+ # @return [JSI::SchemaSet]
292
329
  def request_schemas
293
- request_schema ? [request_schema] : []
330
+ request_schema ? JSI::SchemaSet[request_schema] : JSI::SchemaSet[]
294
331
  end
295
332
 
296
333
  # @param status [Integer, String] response status
@@ -299,7 +336,20 @@ module Scorpio
299
336
  def response_schema(status: , media_type: nil)
300
337
  oa_response = self.oa_response(status: status)
301
338
  oa_response_schema = oa_response ? oa_response['schema'] : nil # Scorpio::OpenAPI::V2::Schema
302
- oa_response_schema ? JSI::Schema.new(oa_response_schema) : nil
339
+ oa_response_schema ? JSI::Schema.ensure_schema(oa_response_schema) : nil
340
+ end
341
+
342
+ # @return [JSI::SchemaSet]
343
+ def response_schemas
344
+ JSI::SchemaSet.build do |schemas|
345
+ if responses
346
+ responses.each_value do |oa_response|
347
+ if oa_response['schema']
348
+ schemas << oa_response['schema']
349
+ end
350
+ end
351
+ end
352
+ end
303
353
  end
304
354
  end
305
355
  end
@@ -1,13 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Scorpio
2
4
  module OpenAPI
3
5
  # OperationsScope acts as an Enumerable of the Operations for an openapi_document,
4
6
  # and offers subscripting by operationId.
5
7
  class OperationsScope
6
- include JSI::Util::Memoize
7
-
8
8
  # @param openapi_document [Scorpio::OpenAPI::Document]
9
9
  def initialize(openapi_document)
10
10
  @openapi_document = openapi_document
11
+ @operations_by_id = Hash.new do |h, operationId|
12
+ op = detect { |operation| operation.operationId == operationId }
13
+ unless op
14
+ raise(::KeyError, "operationId not found: #{operationId.inspect}")
15
+ end
16
+ h[operationId] = op
17
+ end
11
18
  end
12
19
  attr_reader :openapi_document
13
20
 
@@ -23,17 +30,12 @@ module Scorpio
23
30
  end
24
31
  include Enumerable
25
32
 
26
- # @param operationId
27
- # @return [Scorpio::OpenAPI::Operation] the operation with the given operationId
33
+ # finds an operation with the given `operationId`
34
+ # @param operationId [String] the operationId of the operation to find
35
+ # @return [Scorpio::OpenAPI::Operation]
28
36
  # @raise [::KeyError] if the given operationId does not exist
29
37
  def [](operationId)
30
- jsi_memoize(:[], operationId) do |operationId_|
31
- detect { |operation| operation.operationId == operationId_ }.tap do |op|
32
- unless op
33
- raise(::KeyError, "operationId not found: #{operationId_.inspect}")
34
- end
35
- end
36
- end
38
+ @operations_by_id[operationId]
37
39
  end
38
40
  end
39
41
  end
@@ -1,11 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Scorpio
2
4
  module OpenAPI
3
5
  module Reference
4
6
  # overrides JSI::Base#[] to implicitly dereference this Reference, except when
5
7
  # the given token is present in this Reference's instance (this should usually
6
8
  # only apply to the token '$ref')
7
- #
8
- # see JSI::Base#initialize documentation at https://www.rubydoc.info/gems/jsi/JSI/Base
9
9
  def [](token, *a, &b)
10
10
  if respond_to?(:to_hash) && !key?(token)
11
11
  deref do |deref_jsi|
@@ -14,6 +14,31 @@ module Scorpio
14
14
  end
15
15
  return super
16
16
  end
17
+
18
+ # yields or returns the target of this reference
19
+ # @yield [JSI::Base] if a block is given
20
+ # @return [JSI::Base]
21
+ def deref
22
+ return unless respond_to?(:to_hash) && self['$ref'].respond_to?(:to_str)
23
+
24
+ ref_uri = Addressable::URI.parse(self['$ref'])
25
+ ref_uri_nofrag = ref_uri.merge(fragment: nil)
26
+
27
+ if !ref_uri_nofrag.empty? || ref_uri.fragment.nil?
28
+ raise(NotImplementedError,
29
+ "Scorpio currently only supports fragment URIs as OpenAPI references. cannot find reference by uri: #{self['$ref']}"
30
+ )
31
+ end
32
+
33
+ ptr = JSI::Ptr.from_fragment(ref_uri.fragment)
34
+ deref_jsi = ptr.evaluate(jsi_root_node)
35
+
36
+ # TODO type check deref_jsi
37
+
38
+ yield deref_jsi if block_given?
39
+
40
+ deref_jsi
41
+ end
17
42
  end
18
43
  end
19
44
  end
@@ -0,0 +1,15 @@
1
+ module Scorpio
2
+ module OpenAPI
3
+ module Tag
4
+ # operations in the openapi document which have a tag with this tag's name
5
+ # @return [Enumerable<Scorpio::OpenAPI::Operation>]
6
+ def operations
7
+ unless jsi_root_node.is_a?(OpenAPI::Document)
8
+ raise("Tag#operations cannot be used on a Tag that is not inside an OpenAPI document")
9
+ end
10
+
11
+ jsi_root_node.operations.select { |op| op.tags.respond_to?(:to_ary) && op.tags.include?(name) }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Scorpio
2
4
  module OpenAPI
3
5
  module V3
@@ -34,7 +36,7 @@ module Scorpio
34
36
  server_variables = given_server_variables
35
37
  end
36
38
  template = Addressable::Template.new(url)
37
- template.expand(server_variables)
39
+ template.expand(server_variables).freeze
38
40
  end
39
41
  end
40
42
  end