scorpio 0.6.4 → 0.7.0

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -1
  3. data/CHANGELOG.md +10 -2
  4. data/LICENSE.md +2 -4
  5. data/README.md +81 -67
  6. data/documents/{github.com/OAI/OpenAPI-Specification/blob/oas3-schema/schemas/v3.0 → spec.openapis.org/oas/3.0}/schema.yaml +164 -628
  7. data/documents/spec.openapis.org/oas/3.1/dialect/base.schema.yaml +22 -0
  8. data/documents/spec.openapis.org/oas/3.1/meta/base.schema.yaml +71 -0
  9. data/documents/spec.openapis.org/oas/3.1/schema-base.yaml +21 -0
  10. data/documents/spec.openapis.org/oas/3.1/schema.yaml +980 -0
  11. data/documents/swagger.io/v2/schema.json +1 -1
  12. data/documents/www.googleapis.com/discovery/v1/apis/discovery/v1/rest.yml +44 -4
  13. data/lib/scorpio/google_api_document.rb +121 -193
  14. data/lib/scorpio/openapi/document.rb +63 -31
  15. data/lib/scorpio/openapi/operation.rb +114 -96
  16. data/lib/scorpio/openapi/operations_scope.rb +35 -19
  17. data/lib/scorpio/openapi/reference.rb +88 -23
  18. data/lib/scorpio/openapi/schema_elements/type_nullable.rb +38 -0
  19. data/lib/scorpio/openapi/schema_elements.rb +7 -0
  20. data/lib/scorpio/openapi/server.rb +34 -0
  21. data/lib/scorpio/openapi/tag.rb +19 -3
  22. data/lib/scorpio/openapi/v2/dialect.rb +66 -0
  23. data/lib/scorpio/openapi/v2.rb +124 -0
  24. data/lib/scorpio/openapi/v3_0/dialect.rb +76 -0
  25. data/lib/scorpio/openapi/v3_0.rb +130 -0
  26. data/lib/scorpio/openapi/v3_1.rb +243 -0
  27. data/lib/scorpio/openapi.rb +23 -203
  28. data/lib/scorpio/request.rb +67 -61
  29. data/lib/scorpio/resource_base.rb +57 -49
  30. data/lib/scorpio/response.rb +28 -10
  31. data/lib/scorpio/ur.rb +7 -3
  32. data/lib/scorpio/version.rb +1 -1
  33. data/lib/scorpio.rb +12 -6
  34. data/pages/Request_Configuration.md +69 -0
  35. data/pages/Security.md +50 -0
  36. data/scorpio.gemspec +6 -5
  37. metadata +28 -15
  38. data/documents/www.googleapis.com/discovery/v1/apis/discovery/v1/rest +0 -684
  39. data/lib/scorpio/openapi/v3/server.rb +0 -44
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Scorpio
4
- class RequestSchemaFailure < Error
5
- end
6
-
7
4
  class ResourceBase
8
5
  class << self
9
6
  # ResourceBase.inheritable_accessor_defaults is a hash of accessor names (Symbol) mapped
@@ -22,23 +19,20 @@ module Scorpio
22
19
  define_singleton_method(accessor, &default_getter)
23
20
  inheritable_accessor_defaults[accessor] = singleton_class.instance_method(accessor)
24
21
  # field setter method. redefines the getter, replacing the method with one that returns the
25
- # setter's argument (that being inherited to the scope of the define_method(accessor) block
22
+ # setter's argument `value`
26
23
  define_singleton_method(:"#{accessor}=") do |value|
27
- # the setter operates on the singleton class of the receiver (self)
28
- singleton_class.instance_exec(value, self) do |value_, klass|
29
24
  # remove a previous getter. NameError is raised if a getter is not defined on this class;
30
25
  # this may be ignored.
31
26
  begin
32
- remove_method(accessor)
27
+ singleton_class.send(:remove_method, accessor)
33
28
  rescue NameError
34
29
  end
35
30
  # getter method
36
- define_method(accessor) { value_ }
31
+ define_singleton_method(accessor) { value }
37
32
  # invoke on_set callback defined on the class
38
33
  if on_set
39
- klass.instance_exec(&on_set)
34
+ instance_exec(&on_set)
40
35
  end
41
- end
42
36
  end
43
37
  end
44
38
  end
@@ -50,7 +44,7 @@ module Scorpio
50
44
  end
51
45
  update_dynamic_methods
52
46
  else
53
- self.represented_schemas = JSI::SchemaSet.ensure_schema_set(represented_schemas)
47
+ self.represented_schemas = JSI::SchemaSet.new(represented_schemas)
54
48
  end
55
49
  end)
56
50
  define_inheritable_accessor(:models_by_schema, default_value: {}.freeze)
@@ -72,8 +66,8 @@ module Scorpio
72
66
  if openapi_document && openapi_document.v2?
73
67
  raise(ArgumentError, "servers are not supported for OpenAPI V2")
74
68
  end
75
- unless server.is_a?(Scorpio::OpenAPI::V3::Server)
76
- raise(TypeError, "server must be an #{Scorpio::OpenAPI::V3::Server.inspect}. received: #{server.pretty_inspect.chomp}")
69
+ unless server.is_a?(OpenAPI::Server)
70
+ raise(TypeError, -"server must be an #{OpenAPI::Server}. received: #{server.pretty_inspect.chomp}")
77
71
  end
78
72
  })
79
73
 
@@ -91,7 +85,7 @@ module Scorpio
91
85
  end
92
86
 
93
87
  def openapi_document=(openapi_document)
94
- openapi_document = OpenAPI::Document.from_instance(openapi_document)
88
+ openapi_document = Scorpio.new_document(openapi_document)
95
89
 
96
90
  begin
97
91
  singleton_class.instance_exec { remove_method(:openapi_document) }
@@ -106,9 +100,9 @@ module Scorpio
106
100
  define_singleton_method(:openapi_document_class) { openapi_document_class }
107
101
  define_singleton_method(:openapi_document=) do |_|
108
102
  if self == openapi_document_class
109
- raise(ArgumentError, "openapi_document may only be set once on #{inspect}")
103
+ raise(ArgumentError, -"openapi_document may only be set once on #{inspect}")
110
104
  else
111
- raise(ArgumentError, "openapi_document may not be overridden on subclass #{inspect} after it was set on #{openapi_document_class.inspect}")
105
+ raise(ArgumentError, -"openapi_document may not be overridden on subclass #{inspect} after it was set on #{openapi_document_class.inspect}")
112
106
  end
113
107
  end
114
108
  # TODO blame validate openapi_document
@@ -121,7 +115,7 @@ module Scorpio
121
115
 
122
116
  def tag_name=(tag_name)
123
117
  unless tag_name.respond_to?(:to_str)
124
- raise(TypeError, "tag_name must be a string; got: #{tag_name.inspect}")
118
+ raise(TypeError, -"tag_name must be a string; got: #{tag_name.inspect}")
125
119
  end
126
120
  tag_name = tag_name.to_str
127
121
 
@@ -132,7 +126,7 @@ module Scorpio
132
126
  define_singleton_method(:tag_name) { tag_name }
133
127
  define_singleton_method(:tag_name=) do |tag_name|
134
128
  unless tag_name == self.tag_name
135
- raise(ArgumentError, "tag_name may not be overridden (to #{tag_name.inspect}). it is been set to #{self.tag_name.inspect}")
129
+ raise(ArgumentError, -"tag_name may not be overridden (to #{tag_name.inspect}). it is been set to #{self.tag_name.inspect}")
136
130
  end
137
131
  end
138
132
  update_dynamic_methods
@@ -163,13 +157,16 @@ module Scorpio
163
157
  end
164
158
 
165
159
  def operation_for_resource_class?(operation)
166
- return true if tag_name && operation.tags.respond_to?(:to_ary) && operation.tags.include?(tag_name)
160
+ return true if tag_name && operation.tagged?(tag_name)
167
161
 
168
162
  request_response_schemas = operation.request_schemas | operation.response_schemas
169
163
  # TODO/FIX nil instance is wrong. works for $ref and allOf, not for others.
170
164
  # use all inplace applicators, not conditional on instance
171
- all_request_response_schemas = request_response_schemas.each_inplace_applicator_schema(nil)
172
- return true if all_request_response_schemas.any? { |s| represented_schemas.include?(s) }
165
+ request_response_schemas.each do |s|
166
+ s.each_inplace_applicator_schema(nil) do |ias|
167
+ return true if represented_schemas.include?(ias)
168
+ end
169
+ end
173
170
 
174
171
  return false
175
172
  end
@@ -181,11 +178,14 @@ module Scorpio
181
178
  #
182
179
  # TODO/FIX nil instance is wrong. works for $ref and allOf, not for others.
183
180
  # use all inplace applicators, not conditional on instance
184
- all_request_schemas = operation.request_schemas.each_inplace_applicator_schema(nil)
185
- return true if all_request_schemas.any? { |s| represented_schemas.include?(s) }
181
+ operation.request_schemas.each do |s|
182
+ s.each_inplace_applicator_schema(nil) do |ias|
183
+ return true if represented_schemas.include?(ias)
184
+ end
185
+ end
186
186
 
187
187
  # the below only apply if the operation has this resource's tag
188
- return false unless tag_name && operation.tags.respond_to?(:to_ary) && operation.tags.include?(tag_name)
188
+ return false unless tag_name && operation.tagged?(tag_name)
189
189
 
190
190
  # define an instance method if path or query params can be filled in from
191
191
  # property names described by represented_schemas
@@ -223,8 +223,7 @@ module Scorpio
223
223
  # if the operationId is just "addPet"
224
224
  # then the operation's method name on Pet will be "addPet".
225
225
  tag_name_match = tag_name &&
226
- operation.tags.respond_to?(:to_ary) && # TODO maybe operation.tags.valid?
227
- operation.tags.include?(tag_name) &&
226
+ operation.tagged?(tag_name) &&
228
227
  operation.operationId &&
229
228
  operation.operationId.match(/\A#{Regexp.escape(tag_name)}[\.\/\:](\w+)\z/)
230
229
 
@@ -236,9 +235,7 @@ module Scorpio
236
235
  end
237
236
 
238
237
  def update_class_and_instance_api_methods
239
- openapi_document.paths.each do |path, path_item|
240
- path_item.each do |http_method, operation|
241
- next unless operation.is_a?(Scorpio::OpenAPI::Operation)
238
+ openapi_document.operations.each do |operation|
242
239
  method_name = api_method_name_by_operation(operation)
243
240
  if method_name
244
241
  # class method
@@ -255,11 +252,15 @@ module Scorpio
255
252
  end
256
253
  end
257
254
  end
258
- end
259
255
  end
260
256
  end
261
257
 
262
258
  def call_operation(operation, call_params: nil, model_attributes: nil)
259
+ result, _ur = call_operation_ur(operation, call_params: call_params, model_attributes: model_attributes)
260
+ result
261
+ end
262
+
263
+ def call_operation_ur(operation, call_params: nil, model_attributes: nil)
263
264
  call_params = JSI::Util.stringify_symbol_keys(call_params) if call_params.respond_to?(:to_hash)
264
265
  model_attributes = JSI::Util.stringify_symbol_keys(model_attributes || {})
265
266
 
@@ -311,18 +312,19 @@ module Scorpio
311
312
  other_params = call_params
312
313
  end
313
314
 
314
- if operation.request_schema
315
+ request_schema = request.request_schema
316
+ if request_schema
315
317
  request_body_for_schema = -> (o) do
316
318
  if o.is_a?(JSI::Base)
317
319
  # TODO check indicated schemas
318
- if o.jsi_schemas.include?(operation.request_schema)
320
+ if o.jsi_schemas.include?(request_schema)
319
321
  jsi = o
320
322
  else
321
323
  # TODO maybe better way than reinstantiating another jsi as request_schema
322
- jsi = operation.request_schema.new_jsi(o.jsi_node_content)
324
+ jsi = request_schema.new_jsi(o.jsi_node_content)
323
325
  end
324
326
  else
325
- jsi = operation.request_schema.new_jsi(o)
327
+ jsi = request_schema.new_jsi(o)
326
328
  end
327
329
  jsi.jsi_select_descendents_leaf_first do |node|
328
330
  # we want to specifically reject only nodes described (only) by a false schema.
@@ -342,12 +344,19 @@ module Scorpio
342
344
  end
343
345
  else
344
346
  if other_params
345
- if Request.method_with_body?(request.http_method)
347
+ if request.http_method_with_body?
346
348
  request.body_object = other_params
347
349
  else
348
350
  if other_params.respond_to?(:to_hash)
349
- # TODO pay more attention to 'parameters' api method attribute
350
- request.query_params = other_params
351
+ other_params.to_hash.each_pair do |name, value|
352
+ param = request.param_for(name)
353
+ if param
354
+ request.set_param_from(param['in'], param['name'], value)
355
+ else
356
+ # set any params not described by operation params in the query
357
+ request.set_param_from('query', name, value)
358
+ end
359
+ end
351
360
  else
352
361
  raise
353
362
  end
@@ -364,7 +373,7 @@ module Scorpio
364
373
  'source' => {'operationId' => operation.operationId, 'call_params' => call_params, 'url' => ur.request.uri.to_s},
365
374
  'ur' => ur,
366
375
  }
367
- response_object_to_instances(ur.response.body_object, initialize_options)
376
+ [response_object_to_instances(ur.response.body_object(mutable: true), initialize_options), ur]
368
377
  end
369
378
 
370
379
  def response_object_to_instances(object, initialize_options = {})
@@ -375,7 +384,7 @@ module Scorpio
375
384
  elsif models.size == 1
376
385
  model = models.first
377
386
  else
378
- raise(Scorpio::OpenAPI::Error, "multiple models indicated by response JSI. models: #{models.inspect}; object: #{object.pretty_inspect.chomp}")
387
+ raise(Scorpio::OpenAPI::Error, -"multiple models indicated by response JSI. models: #{models.inspect}; object: #{object.pretty_inspect.chomp}")
379
388
  end
380
389
 
381
390
  if model && object.respond_to?(:to_hash)
@@ -422,12 +431,12 @@ module Scorpio
422
431
  end
423
432
 
424
433
  def inspect
425
- "\#<#{self.class.inspect} #{contained_object.inspect}>"
434
+ -"\#<#{self.class.inspect} #{contained_object.inspect}>"
426
435
  end
427
436
 
428
437
  def pretty_print(q)
429
438
  q.instance_exec(self) do |obj|
430
- text "\#<#{obj.class.inspect}"
439
+ text(-"\#<#{obj.class.inspect}")
431
440
  group_sub {
432
441
  nest(2) {
433
442
  breakable ' '
@@ -474,15 +483,14 @@ module Scorpio
474
483
  end
475
484
 
476
485
  def call_operation(operation, call_params: nil)
477
- response = self.class.call_operation(operation, call_params: call_params, model_attributes: self.attributes)
486
+ response, ur = self.class.call_operation_ur(operation, call_params: call_params, model_attributes: self.attributes)
478
487
 
479
488
  # if we're making a POST or PUT and the request schema is this resource, we'll assume that
480
489
  # the request is persisting this resource
481
- request_resource_is_self = operation.request_schema && self.class.represented_schemas.include?(operation.request_schema)
482
- if @options['ur'].is_a?(Scorpio::Ur)
483
- response_schema = @options['ur'].response.response_schema
484
- end
485
- response_resource_is_self = response_schema && self.class.represented_schemas.include?(response_schema)
490
+ request_body_object = ur.scorpio_request.body_object
491
+ request_resource_is_self = request_body_object.is_a?(JSI::Base) &&
492
+ request_body_object.jsi_schemas.any? { |s| self.class.represented_schemas.include?(s) }
493
+ response_resource_is_self = response.is_a?(self.class)
486
494
  if request_resource_is_self && %w(put post).include?(operation.http_method.to_s.downcase)
487
495
  @persisted = true
488
496
 
@@ -557,7 +565,7 @@ module Scorpio
557
565
  ks = k.is_a?(String) ? k :
558
566
  k.is_a?(Symbol) ? k.to_s :
559
567
  k.respond_to?(:to_str) && (kstr = k.to_str).is_a?(String) ? kstr :
560
- raise(TypeError, "JSON object (Hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
568
+ raise(TypeError, -"JSON object (Hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
561
569
  hash[ks] = JSI::Util.as_json(self[k], **options)
562
570
  end
563
571
  hash
@@ -608,7 +616,7 @@ module Scorpio
608
616
  if schema_names.empty?
609
617
  [Container.to_s]
610
618
  else
611
- ["#{Container} (#{schema_names.join(', ')})"]
619
+ [-"#{Container} (#{schema_names.join(', ')})"]
612
620
  end
613
621
  end
614
622
  end
@@ -3,6 +3,8 @@
3
3
  module Scorpio
4
4
  Response = Scorpio::Ur.properties['response']
5
5
 
6
+ # Scorpio::Response is a JSI schema module describing the same instances as [::Ur::Response](https://rubydoc.info/gems/ur/Ur/Response).
7
+ # It relies on methods of that module.
6
8
  module Response
7
9
  # the schema for this response according to its OpenAPI doc
8
10
  # @return [::JSI::Schema]
@@ -10,32 +12,48 @@ module Scorpio
10
12
  ur.scorpio_request.operation.response_schema(status: status, media_type: media_type)
11
13
  end
12
14
 
13
- # the body (String) is parsed according to the response media type, if supported (only JSON is
14
- # currently supported), and instantiated as a JSI instance of {#response_schema} if that is defined.
15
- def body_object
15
+ # media types for which Scorpio has implemented parsing {Response#body_object} from `Response#body`
16
+ SUPPORTED_MEDIA_TYPES = %w(
17
+ application/json
18
+ ).map(&:freeze).freeze
19
+
20
+ # the body (String) is parsed according to the response media type, if
21
+ # supported (see {Response::SUPPORTED_MEDIA_TYPES}), and instantiated
22
+ # as a JSI instance of {#response_schema} if that is defined.
23
+ #
24
+ # @param mutable [Boolean] instantiate the response body object as mutable?
25
+ def body_object(mutable: false)
26
+ k = [:body_object, mutable]
27
+ @memos.fetch(k) { @memos[k] = compute_body_object(mutable: mutable) }
28
+ end
29
+
30
+ private def compute_body_object(mutable: false)
16
31
  if json?
17
32
  if body.empty?
18
33
  # an empty body isn't valid json, of course, but we'll just return nil for it.
19
34
  body_object = nil
20
35
  else
21
36
  begin
22
- body_object = ::JSON.parse(body)
37
+ body_object = JSON.parse(body, freeze: !mutable)
23
38
  #rescue ::JSON::ParserError
24
39
  # TODO
25
40
  end
26
41
  end
27
42
 
28
- if response_schema && (body_object.respond_to?(:to_hash) || body_object.respond_to?(:to_ary))
29
- body_object = response_schema.new_jsi(body_object, mutable: true)
30
- end
43
+ # NOTE: the supported media types above should correspond to Response::SUPPORTED_MEDIA_TYPES
31
44
 
32
- body_object
33
45
  elsif content_type && content_type.type_text? && content_type.subtype?('plain')
34
- body
46
+ body_object = body
35
47
  else
36
48
  # we will return the body if we do not have a supported parsing. for now.
37
- body
49
+ body_object = body
38
50
  end
51
+
52
+ if response_schema && (body_object.respond_to?(:to_hash) || body_object.respond_to?(:to_ary))
53
+ body_object = response_schema.new_jsi(body_object, mutable: mutable)
54
+ end
55
+
56
+ body_object
39
57
  end
40
58
  end
41
59
  end
data/lib/scorpio/ur.rb CHANGED
@@ -12,9 +12,13 @@ module Scorpio
12
12
  }
13
13
  })
14
14
 
15
- -> { Scorpio::Response }.() # invoke autoload
15
+ Scorpio::Response.itself # invoke autoload. this is Scorpio::Ur.properties['response']
16
16
 
17
17
  module Ur
18
+ Request = Ur.properties["request"]
19
+
20
+ # The Scorpio::Request from which this Ur was run
21
+ # @return [Scorpio::Request]
18
22
  attr_accessor :scorpio_request
19
23
 
20
24
  # raises a subclass of Scorpio::HTTPError if the response has an error status.
@@ -24,7 +28,7 @@ module Scorpio
24
28
  # raises a generic HTTPError otherwise.
25
29
  #
26
30
  # @raise [Scorpio::HTTPError]
27
- # @return [void]
31
+ # @return [nil]
28
32
  def raise_on_http_error
29
33
  error_class = Scorpio.error_classes_by_status[response.status]
30
34
  error_class ||= if (400..499).include?(response.status)
@@ -35,7 +39,7 @@ module Scorpio
35
39
  HTTPError
36
40
  end
37
41
  if error_class
38
- message = "Error calling operation #{scorpio_request.operation.human_id}:\n" + response.body
42
+ message = -"Error calling operation #{scorpio_request.operation.human_id}:\n#{response.body}"
39
43
  raise(error_class.new(message).tap do |e|
40
44
  e.ur = self
41
45
  e.response_object = response.body_object
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Scorpio
4
- VERSION = "0.6.4".freeze
4
+ VERSION = "0.7.0".freeze
5
5
  end
data/lib/scorpio.rb CHANGED
@@ -9,6 +9,7 @@ require "pathname"
9
9
  require "pp"
10
10
 
11
11
  module Scorpio
12
+ # @private
12
13
  def self.root
13
14
  @root ||= Pathname.new(__FILE__).dirname.parent.expand_path
14
15
  end
@@ -17,6 +18,7 @@ end
17
18
  module Scorpio
18
19
  # generally put in code paths that are not expected to be valid control flow paths.
19
20
  # rather a NotImplementedCorrectlyError. but that's too long.
21
+ # @private
20
22
  class Bug < NotImplementedError
21
23
  end
22
24
 
@@ -31,12 +33,9 @@ module Scorpio
31
33
  # @param status [Integer] if specified, sets the HTTP status the class represents
32
34
  # @return [Integer] the HTTP status the class represents
33
35
  def self.status(status = nil)
34
- if status
35
- @status = status
36
- Scorpio.error_classes_by_status[status] = self
37
- else
38
- @status
39
- end
36
+ return @status if !status
37
+ Scorpio.error_classes_by_status[status] = self
38
+ @status = status
40
39
  end
41
40
  attr_accessor :ur, :response_object
42
41
  end
@@ -107,4 +106,11 @@ module Scorpio
107
106
  autoload :ResourceBase, 'scorpio/resource_base'
108
107
  autoload :Request, 'scorpio/request'
109
108
  autoload :Response, 'scorpio/response'
109
+
110
+ class << self
111
+ # (see OpenAPI::Document.new_document)
112
+ def new_document(instance, **new_param)
113
+ OpenAPI::Document.new_document(instance, **new_param)
114
+ end
115
+ end
110
116
  end
@@ -0,0 +1,69 @@
1
+ # Request Configuration
2
+
3
+ Scorpio aims to offer flexibility in how applications can configure requests. Requests are initiated from an Operation object (e.g. {Scorpio::OpenAPI::Operation#run}), utilizing the Operation and the OpenAPI document that contains it for configuration. Many configurable attributes can be set with varying granularity, on the document (applying to all requests from all operations, unless overridden), on the operation (applying to all requests from that operation), or on the request itself.
4
+
5
+ Configurable attributes are defined on several modules: for a request, {Scorpio::Request::Configurables}; for an operation, {Scorpio::OpenAPI::Operation::Configurables}; and for a document {Scorpio::OpenAPI::Document::Configurables} and {Scorpio::OpenAPI::Operation::V3Methods::Configurables} or {Scorpio::OpenAPI::Operation::V2Methods::Configurables}.
6
+
7
+ {Scorpio::Request#initialize} and methods that instantiate a request (such as {Scorpio::OpenAPI::Operation#run}) take a keyword hash of configuration, which may include attributes defined on the Configurables module, or parameters defined by the operation (except where parameter names conflict with configurable attributes, or are ambiguous).
8
+
9
+ So for example, given this OpenAPI document:
10
+
11
+ ```yaml
12
+ openapi: 3.1.0
13
+ info: {title: example, version: '1'}
14
+ servers:
15
+ - url: "https://{subdomain}.example.com/v1"
16
+ variables:
17
+ subdomain:
18
+ default: api
19
+ paths:
20
+ '/foos/{id}':
21
+ patch:
22
+ operationId: foos.patch
23
+ parameters:
24
+ - name: id
25
+ in: path
26
+ required: true
27
+ schema:
28
+ type: string
29
+ '/bars/{id}':
30
+ patch:
31
+ operationId: bars.patch
32
+ parameters:
33
+ - name: id
34
+ in: path
35
+ required: true
36
+ schema:
37
+ type: string
38
+ - name: id
39
+ in: query
40
+ schema:
41
+ type: string
42
+ ```
43
+
44
+ then the following Ruby code demonstrates how configuration is inherited from document to operation to request and how parameters are applied.
45
+
46
+ Note that `build_request` takes the same parameters as `run` or `run_ur` would; it is used since we are not hitting a real API.
47
+
48
+ ```ruby
49
+ # instantiate OAD whose content is the same as the yaml above
50
+ oad = Scorpio::OpenAPI::Document.from_instance({"openapi"=>"3.1.0", "info"=>{"title"=>"example", "version"=>"1"}, "servers"=>[{"url"=>"https://{subdomain}.example.com/v1", "variables"=>{"subdomain"=>{"default"=>"api"}}}], "paths"=>{"/foos/{id}"=>{"patch"=>{"operationId"=>"foos.patch", "parameters"=>[{"name"=>"id", "in"=>"path", "required"=>true, "schema"=>{"type"=>"string"}}]}}, "/bars/{id}"=>{"patch"=>{"operationId"=>"bars.patch", "parameters"=>[{"name"=>"id", "in"=>"path", "required"=>true, "schema"=>{"type"=>"string"}}, {"name"=>"id", "in"=>"query", "schema"=>{"type"=>"string"}}]}}}})
51
+
52
+ # configure document level server subdomain
53
+ oad.server_variables = {subdomain: 'east'}
54
+ # configure operation level server subdomain
55
+ oad.operations['bars.patch'].server_variables = {subdomain: 'north'}
56
+
57
+ # uses document server_variables and sets id as request path param
58
+ oad.operations['foos.patch'].build_request(id: 'C7').url
59
+ # https://east.example.com/v1/foos/C7
60
+
61
+ # sets id as request path param and sets server_variables request config
62
+ oad.operations['foos.patch'].build_request(id: 'C7', server_variables: {subdomain: 'west'}).url
63
+ # https://west.example.com/v1/foos/C7
64
+
65
+ # uses operation server_variables. since `id` is a param name in both path and query, `id` alone
66
+ # cannot be used as configuration; it must be set on the appropriate params' configurations
67
+ oad.operations['bars.patch'].build_request(path_params: {id: 'C7'}, query_params: {id: 'D7'}).url
68
+ # https://north.example.com/v1/bars/C7?id=D7
69
+ ```
data/pages/Security.md ADDED
@@ -0,0 +1,50 @@
1
+ # API Security
2
+
3
+ Scorpio does not currently implement an interface for any particular API security mechanism, which an OpenAPI description might specify using an operation's Security Requirement and corresponding Security Scheme. Scorpio offers flexibility in how applications may authenticate using common mechanisms - for more general guidance see {file:Request_Configuration Request Configuration}.
4
+
5
+ ### Authorization header
6
+
7
+ An authorization header can be set using the `authorization` configuration on an OpenAPI document, operation, or request:
8
+
9
+ ```ruby
10
+ # applies to every request (unless overridden per operation or request)
11
+ my_openapi_document.authorization = "Basic bm90RXRoYW46cDRzc3cwcmQ"
12
+
13
+ # or, applying to every request from my_operation (unless overridden)
14
+ my_operation.authorization = "Basic bm90RXRoYW46cDRzc3cwcmQ"
15
+
16
+ # or, per request:
17
+ my_operation.run(authorization: "Basic bm90RXRoYW46cDRzc3cwcmQ")
18
+ ```
19
+
20
+ ### Another header
21
+
22
+ Any other request headers can be set similarly:
23
+
24
+ ```ruby
25
+ # document
26
+ my_openapi_document.request_headers = {"Api-Key" => "bm90RXRoYW46cDRzc3cwcmQ"}
27
+
28
+ # operation
29
+ my_operation.request_headers = {"Api-Key" => "bm90RXRoYW46cDRzc3cwcmQ"}
30
+
31
+ # request (note: just `headers` not `request_headers` here)
32
+ my_operation.run(headers: {"Api-Key" => "bm90RXRoYW46cDRzc3cwcmQ"})
33
+ ```
34
+
35
+ ### Faraday request middleware
36
+
37
+ Faraday middleware can be used to set headers or other components of the request, by configuring `faraday_builder`. If authentication must be computed for each request (e.g. by including a signature of the request), configured Faraday middleware is invoked for each request. Faraday middleware libraries exist for common authentication mechanisms - see [awesome-faraday](https://github.com/lostisland/awesome-faraday).
38
+
39
+ ```ruby
40
+ # document
41
+ my_openapi_document.faraday_builder = proc do |faraday_connection|
42
+ faraday_connection.request(:authorization, :basic, 'notEthan', 'p4ssw0rd')
43
+ end
44
+
45
+ # operation
46
+ my_operation.faraday_builder = proc { |c| c.request(:authorization, :basic, 'notEthan', 'p4ssw0rd') }
47
+
48
+ # request
49
+ my_operation.run(faraday_builder: proc { |c| c.request(:authorization, :basic, 'notEthan', 'p4ssw0rd') })
50
+ ```
data/scorpio.gemspec CHANGED
@@ -6,10 +6,10 @@ Gem::Specification.new do |spec|
6
6
  spec.authors = ["Ethan"]
7
7
  spec.email = ["ethan@unth.net"]
8
8
 
9
- spec.summary = 'Scorpio REST client'
10
- spec.description = 'ORM style REST client'
9
+ spec.summary = "Scorpio web API client"
10
+ spec.description = "Scorpio offers a client interface to any web API described by an OpenAPI description, following ORM conventions for RESTful resources."
11
11
  spec.homepage = "https://github.com/notEthan/scorpio"
12
- spec.license = "AGPL-3.0"
12
+ spec.license = "AGPL-3.0-only"
13
13
 
14
14
  spec.files = [
15
15
  'LICENSE.md',
@@ -19,12 +19,13 @@ Gem::Specification.new do |spec|
19
19
  'scorpio.gemspec',
20
20
  *Dir['lib/**/*'],
21
21
  *Dir['documents/**/*'],
22
+ *Dir['pages/**/*'],
22
23
  ].reject { |f| File.lstat(f).ftype == 'directory' }
23
24
 
24
25
  spec.require_paths = ["lib"]
25
26
 
26
- spec.add_dependency "jsi", "~> 0.8.1"
27
+ spec.add_dependency("jsi", "~> 0.9")
27
28
  spec.add_dependency "ur", "~> 0.2.5"
28
- spec.add_dependency "faraday", "< 3.0"
29
+ spec.add_dependency "faraday"
29
30
  spec.add_dependency "addressable", '~> 2.3'
30
31
  end