jsapi 2.1.2 → 3.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.
- checksums.yaml +4 -4
- data/lib/jsapi/client/base.rb +182 -0
- data/lib/jsapi/client/exceptions.rb +45 -0
- data/lib/jsapi/client/request.rb +204 -0
- data/lib/jsapi/client/response.rb +120 -0
- data/lib/jsapi/client/session/base.rb +61 -0
- data/lib/jsapi/client/session/logger.rb +52 -0
- data/lib/jsapi/client/session/single_request.rb +16 -0
- data/lib/jsapi/client/session/test/class_methods.rb +49 -0
- data/lib/jsapi/client/session/test/response.rb +56 -0
- data/lib/jsapi/client/session/test/spy.rb +43 -0
- data/lib/jsapi/client/session/test.rb +17 -0
- data/lib/jsapi/client/session.rb +6 -0
- data/lib/jsapi/client.rb +7 -0
- data/lib/jsapi/configuration.rb +2 -2
- data/lib/jsapi/controller/authentication/credentials/http/bearer.rb +5 -12
- data/lib/jsapi/controller/authentication/credentials.rb +4 -4
- data/lib/jsapi/controller/authentication.rb +3 -3
- data/lib/jsapi/controller/methods.rb +43 -77
- data/lib/jsapi/controller/parameters.rb +6 -6
- data/lib/jsapi/controller/response.rb +7 -7
- data/lib/jsapi/{meta/definitions.rb → definition/api.rb} +62 -40
- data/lib/jsapi/{meta → definition}/callable/symbol_evaluator.rb +1 -1
- data/lib/jsapi/{meta → definition}/callable/symbol_sequence_evaluator.rb +1 -1
- data/lib/jsapi/{meta → definition}/callable.rb +1 -1
- data/lib/jsapi/{meta → definition}/callback/base.rb +5 -5
- data/lib/jsapi/{meta → definition}/callback/reference.rb +1 -1
- data/lib/jsapi/{meta → definition}/callback.rb +1 -1
- data/lib/jsapi/{meta → definition}/contact.rb +1 -1
- data/lib/jsapi/{meta → definition}/content.rb +11 -11
- data/lib/jsapi/{meta → definition}/defaults.rb +1 -1
- data/lib/jsapi/{meta → definition}/example/base.rb +1 -1
- data/lib/jsapi/{meta → definition}/example/builder.rb +4 -4
- data/lib/jsapi/{meta → definition}/example/reference.rb +1 -1
- data/lib/jsapi/{meta → definition}/example.rb +1 -1
- data/lib/jsapi/{meta → definition}/existence.rb +1 -1
- data/lib/jsapi/{meta → definition}/external_documentation.rb +1 -1
- data/lib/jsapi/{meta → definition}/header/base.rb +1 -1
- data/lib/jsapi/{meta → definition}/header/reference.rb +1 -1
- data/lib/jsapi/{meta → definition}/header.rb +1 -1
- data/lib/jsapi/{meta → definition}/info.rb +1 -1
- data/lib/jsapi/{meta → definition}/license.rb +1 -1
- data/lib/jsapi/{meta → definition}/link/base.rb +1 -1
- data/lib/jsapi/{meta → definition}/link/reference.rb +1 -1
- data/lib/jsapi/{meta → definition}/link.rb +1 -1
- data/lib/jsapi/{meta → definition}/model/attributes/class_methods.rb +1 -1
- data/lib/jsapi/{meta → definition}/model/attributes/frozen_error.rb +1 -1
- data/lib/jsapi/{meta → definition}/model/attributes/type_caster.rb +2 -2
- data/lib/jsapi/{meta → definition}/model/attributes.rb +1 -1
- data/lib/jsapi/{meta → definition}/model/base.rb +1 -1
- data/lib/jsapi/{meta → definition}/model/reference.rb +11 -11
- data/lib/jsapi/{meta → definition}/model/wrappable.rb +3 -3
- data/lib/jsapi/{meta → definition}/model/wrapper.rb +5 -5
- data/lib/jsapi/{meta → definition}/oauth_flow.rb +1 -1
- data/lib/jsapi/{meta → definition}/openapi/extensions.rb +1 -1
- data/lib/jsapi/{meta → definition}/openapi/path_item.rb +5 -5
- data/lib/jsapi/{meta → definition}/openapi/version.rb +2 -2
- data/lib/jsapi/{meta → definition}/openapi.rb +1 -1
- data/lib/jsapi/{meta → definition}/operation.rb +108 -62
- data/lib/jsapi/{meta → definition}/parameter/base.rb +11 -11
- data/lib/jsapi/{meta → definition}/parameter/reference.rb +5 -5
- data/lib/jsapi/{meta → definition}/parameter/wrapper.rb +2 -2
- data/lib/jsapi/{meta → definition}/parameter.rb +1 -1
- data/lib/jsapi/{meta → definition}/path.rb +1 -1
- data/lib/jsapi/{meta → definition}/pathname.rb +1 -1
- data/lib/jsapi/{meta → definition}/property.rb +2 -2
- data/lib/jsapi/{meta → definition}/reference_error.rb +1 -1
- data/lib/jsapi/{meta → definition}/request_body/base.rb +1 -1
- data/lib/jsapi/{meta → definition}/request_body/reference.rb +1 -1
- data/lib/jsapi/{meta → definition}/request_body/wrapper.rb +2 -2
- data/lib/jsapi/{meta → definition}/request_body.rb +1 -1
- data/lib/jsapi/{meta → definition}/rescue_handler.rb +1 -1
- data/lib/jsapi/{meta → definition}/response/base.rb +4 -4
- data/lib/jsapi/{meta → definition}/response/reference.rb +1 -1
- data/lib/jsapi/{meta → definition}/response/wrapper.rb +17 -5
- data/lib/jsapi/{meta → definition}/response.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/additional_properties.rb +2 -2
- data/lib/jsapi/{meta → definition}/schema/array.rb +2 -2
- data/lib/jsapi/{meta → definition}/schema/base.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/boolean.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/boundary.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/conversion.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/discriminator.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/integer.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/number.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/numeric.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/object.rb +13 -13
- data/lib/jsapi/{meta → definition}/schema/reference.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/string.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/validation/base.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/validation/enum.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/validation/max_items.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/validation/max_length.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/validation/maximum.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/validation/min_items.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/validation/min_length.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/validation/minimum.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/validation/multiple_of.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/validation/pattern.rb +1 -1
- data/lib/jsapi/{meta → definition}/schema/wrapper.rb +18 -4
- data/lib/jsapi/{meta → definition}/schema.rb +5 -5
- data/lib/jsapi/{meta → definition}/security_requirement.rb +1 -1
- data/lib/jsapi/{meta → definition}/security_scheme/api_key.rb +1 -1
- data/lib/jsapi/{meta → definition}/security_scheme/base.rb +1 -1
- data/lib/jsapi/{meta → definition}/security_scheme/http/basic.rb +1 -1
- data/lib/jsapi/{meta → definition}/security_scheme/http/bearer.rb +1 -1
- data/lib/jsapi/{meta → definition}/security_scheme/http/other.rb +1 -1
- data/lib/jsapi/{meta → definition}/security_scheme/http.rb +1 -1
- data/lib/jsapi/{meta → definition}/security_scheme/mutual_tls.rb +1 -1
- data/lib/jsapi/{meta → definition}/security_scheme/oauth2.rb +1 -1
- data/lib/jsapi/{meta → definition}/security_scheme/open_id_connect.rb +1 -1
- data/lib/jsapi/{meta → definition}/security_scheme.rb +1 -1
- data/lib/jsapi/{meta → definition}/server.rb +24 -1
- data/lib/jsapi/{meta → definition}/server_variable.rb +1 -1
- data/lib/jsapi/{meta → definition}/tag.rb +1 -1
- data/lib/jsapi/definition.rb +33 -0
- data/lib/jsapi/dsl/{definitions.rb → api.rb} +48 -48
- data/lib/jsapi/dsl/base.rb +7 -7
- data/lib/jsapi/dsl/class_methods.rb +34 -32
- data/lib/jsapi/dsl/examples.rb +1 -1
- data/lib/jsapi/dsl/operation.rb +9 -7
- data/lib/jsapi/dsl/parameter.rb +1 -1
- data/lib/jsapi/dsl/path.rb +8 -6
- data/lib/jsapi/dsl/response.rb +5 -5
- data/lib/jsapi/dsl/schema.rb +17 -17
- data/lib/jsapi/dsl/shared_operation_methods.rb +11 -26
- data/lib/jsapi/dsl.rb +2 -2
- data/lib/jsapi/model/errors.rb +3 -3
- data/lib/jsapi/status/code.rb +20 -0
- data/lib/jsapi/support/messages.rb +27 -0
- data/lib/jsapi/support.rb +3 -0
- data/lib/jsapi/version.rb +1 -1
- data/lib/jsapi.rb +3 -3
- metadata +115 -102
- data/lib/jsapi/jsonify_error.rb +0 -17
- data/lib/jsapi/messages.rb +0 -19
- data/lib/jsapi/meta.rb +0 -38
- /data/lib/jsapi/{meta → definition}/model.rb +0 -0
- /data/lib/jsapi/{meta → definition}/schema/validation.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0ec240995632c56faf46229f425a0fb65a32da97f6aa0b68ee66327a6a17ca3b
|
|
4
|
+
data.tar.gz: 7c457efa33d29830814b66dc62ac001d813f042890b7e637e265567198251e01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a7fc689d67fe755b7351a06730ff59b65212a5764e4eeab2d2b03748954e6c635a371bbdab0615542d07e0226c96c81453e0f246f99ef007f94f1b5c6210a54
|
|
7
|
+
data.tar.gz: 2cbe363c8e5915611b9c3e9a38c2448af1bc0ccb8353b99a6f0595c51a115dce93269b2832a1f18d915f11dd96855249261f7749ba2ca9175685deda51cacbe6
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
|
|
6
|
+
module Jsapi
|
|
7
|
+
module Client
|
|
8
|
+
# Base class for response errors.
|
|
9
|
+
class ResponseError < StandardError; end
|
|
10
|
+
|
|
11
|
+
# Raised when the response body is invalid.
|
|
12
|
+
class InvalidResponseBodyError < ResponseError
|
|
13
|
+
|
|
14
|
+
# The Response::Body that caused the error.
|
|
15
|
+
attr_reader :response_body
|
|
16
|
+
|
|
17
|
+
def initialize(response_body) # :nodoc:
|
|
18
|
+
super(Support::Messages.join(*response_body.errors.full_messages))
|
|
19
|
+
@response_body = response_body
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Raised when no response specification matches the status code returned by the server.
|
|
24
|
+
class UnexpectedResponseError < ResponseError
|
|
25
|
+
def initialize(operation, code) # :nodoc:
|
|
26
|
+
super("Unexpected status code #{code} for operation #{operation.name.inspect}")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# \Base client class.
|
|
31
|
+
class Base
|
|
32
|
+
include DSL
|
|
33
|
+
include Exceptions
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# :attr: logger
|
|
37
|
+
attr_accessor :logger
|
|
38
|
+
|
|
39
|
+
##
|
|
40
|
+
# :attr_reader: request_headers
|
|
41
|
+
# The Request::Headers set for every subsequent call.
|
|
42
|
+
#
|
|
43
|
+
# request_headers['User-Agent'] = '...'
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# :call-seq:
|
|
47
|
+
# new(base_uri, http: nil)
|
|
48
|
+
# new(server_name, variables = nil, http: nil)
|
|
49
|
+
#
|
|
50
|
+
# Creates a client.
|
|
51
|
+
#
|
|
52
|
+
# Raises an +ArgumentError+ if +base_uri+ or +:http+ is invalid.
|
|
53
|
+
def initialize(arg1, arg2 = nil, http: nil)
|
|
54
|
+
unless arg1.is_a?(URI)
|
|
55
|
+
server_url = self.class.api.server(arg1)&.url_for(arg2)
|
|
56
|
+
arg1 = server_url unless server_url.nil?
|
|
57
|
+
arg1 = URI(arg1)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
@base_uri = arg1
|
|
61
|
+
@host = @base_uri.host
|
|
62
|
+
@port = @base_uri.port
|
|
63
|
+
|
|
64
|
+
http = http&.dup || {}
|
|
65
|
+
raise ArgumentError, ':http must be a hash' unless http.is_a?(Hash)
|
|
66
|
+
|
|
67
|
+
@http_options = http
|
|
68
|
+
@http_options[:use_ssl] = true if @base_uri.scheme == 'https'
|
|
69
|
+
rescue URI::InvalidURIError => e
|
|
70
|
+
raise ArgumentError, e
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# :method: call
|
|
75
|
+
# :call-seq:
|
|
76
|
+
# call(operation_name = nil, body = nil, content_type = nil, **params, &block)
|
|
77
|
+
#
|
|
78
|
+
# Calls a remote API operation and returns a Response. Returns +nil+ if no response
|
|
79
|
+
# specification matches the status code returned by the server.
|
|
80
|
+
#
|
|
81
|
+
# If a block is given it is called with a Request as argument.
|
|
82
|
+
#
|
|
83
|
+
# Examples:
|
|
84
|
+
#
|
|
85
|
+
# call('foo', id: 1)
|
|
86
|
+
#
|
|
87
|
+
# call('foo') do |request|
|
|
88
|
+
# request[:id] = 1
|
|
89
|
+
# request.body = foo
|
|
90
|
+
# end
|
|
91
|
+
#
|
|
92
|
+
# +operation_name+ specifies the operation to be called. It can be omitted if the
|
|
93
|
+
# remote API exposes a single operation. If the operation does not exist, an
|
|
94
|
+
# OperationNotFound exception is raised.
|
|
95
|
+
#
|
|
96
|
+
# Raises a RequestError if the HTTP request cannot be be built from the given data.
|
|
97
|
+
#
|
|
98
|
+
# Raises a Session::Error if a session-level error occurs.
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
# :method: call!
|
|
102
|
+
# :call-seq:
|
|
103
|
+
# call!(operation_name = nil, body = nil, content_type = nil, **params, &block)
|
|
104
|
+
#
|
|
105
|
+
# Like +call+, except it raises a subclass of ResponseError when:
|
|
106
|
+
#
|
|
107
|
+
# - InvalidResponseBodyError - raised when the response body is invalid.
|
|
108
|
+
# - UnexpectedResponseError - raised when no response specification matches the
|
|
109
|
+
# status code returned by the server.
|
|
110
|
+
|
|
111
|
+
[true, false].each do |bang|
|
|
112
|
+
define_method(bang ? :call! : :call) \
|
|
113
|
+
do |operation_name = nil, body = nil, content_type = nil, **params, &block|
|
|
114
|
+
operation = self.class.api.find_operation!(operation_name)
|
|
115
|
+
|
|
116
|
+
request = Request.new(@base_uri, operation, headers: @request_headers, params: params)
|
|
117
|
+
request.content_type = content_type || 'application/json'
|
|
118
|
+
request.body = body
|
|
119
|
+
instance_exec(request, &block) if block
|
|
120
|
+
|
|
121
|
+
http_request = request.http_request
|
|
122
|
+
|
|
123
|
+
session = @session || Session::SingleRequest.new(@host, @port, @logger, **@http_options)
|
|
124
|
+
http_response = session.send_request(http_request)
|
|
125
|
+
|
|
126
|
+
response_definition = operation.find_response_for(http_response.code)
|
|
127
|
+
if response_definition.nil?
|
|
128
|
+
raise UnexpectedResponseError.new(operation, http_response.code) if bang
|
|
129
|
+
|
|
130
|
+
return
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
response = Response.new(http_response, response_definition)
|
|
134
|
+
raise InvalidResponseBodyError.new(response.body) if bang && response.body.invalid?
|
|
135
|
+
|
|
136
|
+
error_class = self.class.error_class_for(response.status_code)
|
|
137
|
+
raise error_class, response.error_message if error_class
|
|
138
|
+
|
|
139
|
+
response
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Closes the current session.
|
|
144
|
+
def finish_session
|
|
145
|
+
@session&.finish
|
|
146
|
+
@session = nil
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def inspect # :nodoc:
|
|
150
|
+
"#<#{self.class.name} base_uri: #{@base_uri}, http: #{@http_options.inspect}>"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def request_headers # :nodoc:
|
|
154
|
+
@request_headers ||= Request::Headers.new
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Starts a session. If a block is given the session is closed automatically
|
|
158
|
+
# after the block has been executed.
|
|
159
|
+
#
|
|
160
|
+
# Returns the value returned by the block or +nil+ if no block is given.
|
|
161
|
+
#
|
|
162
|
+
# start_session do
|
|
163
|
+
# call('foo')
|
|
164
|
+
# call('bar')
|
|
165
|
+
# end
|
|
166
|
+
#
|
|
167
|
+
# Raises a Session::Error if a session-level error occurs.
|
|
168
|
+
def start_session
|
|
169
|
+
@session&.finish
|
|
170
|
+
@session = Session::Base.new(@host, @port, @logger, **@http_options)
|
|
171
|
+
|
|
172
|
+
if block_given?
|
|
173
|
+
begin
|
|
174
|
+
yield
|
|
175
|
+
ensure
|
|
176
|
+
finish_session
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jsapi
|
|
4
|
+
module Client
|
|
5
|
+
module Exceptions
|
|
6
|
+
module ClassMethods
|
|
7
|
+
# Registers +error_class+ to be raised when the server responds with a status code
|
|
8
|
+
# matching +:on+.
|
|
9
|
+
#
|
|
10
|
+
# +error_class+ can be +StandardError+ or a subclass of it. +:on+ can be a status
|
|
11
|
+
# code or a range of status codes.
|
|
12
|
+
#
|
|
13
|
+
# class ServerError < StandardError; end
|
|
14
|
+
# class InternalServerError < ServerError; end
|
|
15
|
+
#
|
|
16
|
+
# raises ServerError, on: '5xx'
|
|
17
|
+
# raises InternalServerError, on: '500'
|
|
18
|
+
#
|
|
19
|
+
# Raises an +ArgumentError+ if +error_class+ or +:on+ is invalid.
|
|
20
|
+
def raises(error_class, on:)
|
|
21
|
+
raise ArgumentError, "#{error_class.inspect} isn't a class" \
|
|
22
|
+
unless error_class.is_a?(Class)
|
|
23
|
+
|
|
24
|
+
raise ArgumentError, "#{error_class.inspect} isn't a subclass of StandardError" \
|
|
25
|
+
unless error_class <= StandardError
|
|
26
|
+
|
|
27
|
+
(@_raises ||= {})[Status.from(on)] = error_class
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def error_class_for(status_code) # :nodoc:
|
|
31
|
+
status_code = Status::Code.from(status_code)
|
|
32
|
+
|
|
33
|
+
@_raises
|
|
34
|
+
&.sort
|
|
35
|
+
&.find { |status, _| status.match?(status_code) }
|
|
36
|
+
&.second || superclass.try(:error_class_for, status_code)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.included(base) # :nodoc:
|
|
41
|
+
base.extend(ClassMethods)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jsapi
|
|
4
|
+
module Client
|
|
5
|
+
class RequestError < StandardError; end
|
|
6
|
+
|
|
7
|
+
class Request
|
|
8
|
+
class Headers
|
|
9
|
+
delegate :each, to: :@headers
|
|
10
|
+
|
|
11
|
+
def initialize(headers = nil) # :nodoc:
|
|
12
|
+
@headers = {}
|
|
13
|
+
headers&.each { |name, value| self[name] = value }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns the value for +name+.
|
|
17
|
+
def [](name)
|
|
18
|
+
@headers[name.to_s.downcase]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Sets the value for +name+ to +value+.
|
|
22
|
+
#
|
|
23
|
+
# Raises an +ArgumentError+ if name is blank.
|
|
24
|
+
def []=(name, value)
|
|
25
|
+
name = name.to_s.downcase
|
|
26
|
+
raise ArgumentError, "name can't be blank" if name.blank?
|
|
27
|
+
|
|
28
|
+
if value.nil?
|
|
29
|
+
@headers.delete(name)
|
|
30
|
+
else
|
|
31
|
+
@headers[name] = value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Sets the username and password for HTTP Basic Authentication.
|
|
36
|
+
def http_basic_auth(username = nil, password = nil)
|
|
37
|
+
auth_param = Base64.strict_encode64("#{username}:#{password}")
|
|
38
|
+
@headers['authorization'] = "Basic #{auth_param}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def inspect # :nodoc:
|
|
42
|
+
"#<#{
|
|
43
|
+
[
|
|
44
|
+
self.class.name,
|
|
45
|
+
@headers.map { |name, value| "#{name}: #{value.inspect}" }.sort.join(', ')
|
|
46
|
+
].compact_blank.join(' ')
|
|
47
|
+
}>"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class Parameters
|
|
52
|
+
def initialize(params = nil)
|
|
53
|
+
@params = {}
|
|
54
|
+
params&.each { |name, value| self[name] = value }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns the value for +name+.
|
|
58
|
+
def [](name)
|
|
59
|
+
@params[name.to_s.underscore]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Sets the value for +name+ to +value+.
|
|
63
|
+
#
|
|
64
|
+
# Raises an +ArgumentError+ if name is blank.
|
|
65
|
+
def []=(name, value)
|
|
66
|
+
name = name.to_s
|
|
67
|
+
raise ArgumentError, "name can't be blank" if name.blank?
|
|
68
|
+
|
|
69
|
+
@params[name.underscore] = value
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def inspect # :nodoc:
|
|
73
|
+
"#<#{
|
|
74
|
+
[
|
|
75
|
+
self.class.name,
|
|
76
|
+
@params.map { |name, value| "#{name}: #{value.inspect}" }.sort.join(', ')
|
|
77
|
+
].compact_blank.join(' ')
|
|
78
|
+
}>"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The content type.
|
|
83
|
+
attr_accessor :content_type
|
|
84
|
+
|
|
85
|
+
# The body.
|
|
86
|
+
attr_accessor :body
|
|
87
|
+
|
|
88
|
+
# The Headers.
|
|
89
|
+
attr_reader :headers
|
|
90
|
+
|
|
91
|
+
##
|
|
92
|
+
# :method: [](name)
|
|
93
|
+
# Returns the parameter value for +name+.
|
|
94
|
+
|
|
95
|
+
##
|
|
96
|
+
# :method: []=(name, value)
|
|
97
|
+
# Sets the parameter value for +name+ to +value+.
|
|
98
|
+
|
|
99
|
+
delegate :[], :[]=, to: :@params
|
|
100
|
+
|
|
101
|
+
def initialize(base_uri, operation, headers: nil, params: nil) # :nodoc:
|
|
102
|
+
@base_uri = base_uri
|
|
103
|
+
@operation = operation
|
|
104
|
+
@headers = Headers.new(headers)
|
|
105
|
+
@params = Parameters.new(params)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def http_request # :nodoc:
|
|
109
|
+
create_http_request.tap do |http_request|
|
|
110
|
+
http_headers.each { |name, value| http_request[name] = value }
|
|
111
|
+
|
|
112
|
+
if (request_body_definition = @operation.request_body)
|
|
113
|
+
media_type = Media::Type.from(content_type || 'application/json')
|
|
114
|
+
|
|
115
|
+
http_request.content_type = media_type.to_s
|
|
116
|
+
http_request.body =
|
|
117
|
+
if media_type.json?
|
|
118
|
+
request_body_definition
|
|
119
|
+
.content_for(media_type)
|
|
120
|
+
.schema
|
|
121
|
+
.jsonify(body, context: :request, omit: :empty)
|
|
122
|
+
.to_json
|
|
123
|
+
else
|
|
124
|
+
body.to_s
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
rescue StandardError => e
|
|
129
|
+
raise RequestError, e.message
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def inspect # :nodoc:
|
|
133
|
+
"#<#{self.class.name} " \
|
|
134
|
+
"base_uri: #{@base_uri}, " \
|
|
135
|
+
"operation: #{@operation.name.inspect}, " \
|
|
136
|
+
"headers: #{@headers.inspect}, " \
|
|
137
|
+
"parameters: #{@params.inspect}>"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
def create_http_request
|
|
143
|
+
http_method = @operation.http_method
|
|
144
|
+
|
|
145
|
+
# Try to instantiate a subclass of Net::HTTPRequest
|
|
146
|
+
http_request_class = "Net::HTTP::#{http_method.camelize}".safe_constantize
|
|
147
|
+
return http_request_class.new(http_request_uri) if http_request_class
|
|
148
|
+
|
|
149
|
+
# Fallback to Net::HTTPGenericRequest even it should not be used directly
|
|
150
|
+
Net::HTTPGenericRequest.new(
|
|
151
|
+
http_method,
|
|
152
|
+
@operation.request_body.present?,
|
|
153
|
+
@operation.responses.values.lazy.any?(&:content?),
|
|
154
|
+
http_request_uri
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def http_request_uri
|
|
159
|
+
path = File.join(@base_uri.path, @operation.full_path.to_s)
|
|
160
|
+
|
|
161
|
+
# Substitute variables in RFC 6570 format
|
|
162
|
+
path.gsub!(/\{\w+\}/) do |match|
|
|
163
|
+
@params[match[1..-2]].tap do |value|
|
|
164
|
+
# Raise an error as a URI must not contain curly brackets.
|
|
165
|
+
raise "#{match.inspect} cannot be substituted" if value.nil?
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Merge base URI and path and add query parameters
|
|
170
|
+
@base_uri.merge(path).tap do |uri|
|
|
171
|
+
if (querystring_param = @operation.querystring_parameter)
|
|
172
|
+
querystring = @params[querystring_param.name]
|
|
173
|
+
uri.query = querystring.to_s unless querystring.nil?
|
|
174
|
+
elsif (query_params = @operation.query_parameters).any?
|
|
175
|
+
uri.query = query_params.transform_values do |parameter|
|
|
176
|
+
parameter.schema.jsonify(
|
|
177
|
+
@params[parameter.name],
|
|
178
|
+
context: :request,
|
|
179
|
+
omit: :empty
|
|
180
|
+
)
|
|
181
|
+
rescue Definition::Schema::JsonifyError => e
|
|
182
|
+
raise e.prepend(parameter.name)
|
|
183
|
+
end.compact.to_query
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def http_headers
|
|
189
|
+
Headers.new.tap do |headers|
|
|
190
|
+
@operation.header_parameters.each do |name, header_definition|
|
|
191
|
+
value = @params[name] || @headers[name]
|
|
192
|
+
next if value.nil?
|
|
193
|
+
|
|
194
|
+
value = Array(value).join(',') if header_definition.schema.array?
|
|
195
|
+
headers[name] = value
|
|
196
|
+
end
|
|
197
|
+
@headers.each do |name, value|
|
|
198
|
+
headers[name] ||= value
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jsapi
|
|
4
|
+
module Client
|
|
5
|
+
class Response
|
|
6
|
+
class Body
|
|
7
|
+
attr_reader :content, :errors
|
|
8
|
+
|
|
9
|
+
def initialize(http_response_body, media_type, schema) # :nodoc:
|
|
10
|
+
@http_response_body = http_response_body
|
|
11
|
+
@errors = Model::Errors.new(self)
|
|
12
|
+
@content =
|
|
13
|
+
begin
|
|
14
|
+
body = http_response_body
|
|
15
|
+
body = ::JSON.parse(body) if media_type.json?
|
|
16
|
+
|
|
17
|
+
if schema
|
|
18
|
+
JSON.wrap(body, schema, context: :response).tap do |json|
|
|
19
|
+
json.validate(@errors)
|
|
20
|
+
end.value
|
|
21
|
+
else
|
|
22
|
+
body
|
|
23
|
+
end
|
|
24
|
+
rescue ::JSON::ParserError => e
|
|
25
|
+
@errors.add(:base, :invalid, message: e.message)
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def inspect # :nodoc:
|
|
31
|
+
"#<#{self.class.name} #{@http_response_body.inspect}>"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def invalid?
|
|
35
|
+
!valid?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def valid?
|
|
39
|
+
errors.none?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class Headers
|
|
44
|
+
##
|
|
45
|
+
# :method: each
|
|
46
|
+
delegate :each, to: :@headers
|
|
47
|
+
|
|
48
|
+
def initialize(http_response, response_definition) # :nodoc:
|
|
49
|
+
@headers = http_response.each_header.to_h do |name, value|
|
|
50
|
+
if (header_definition = response_definition.header_for(name))
|
|
51
|
+
schema = header_definition.schema
|
|
52
|
+
value = value.split(',') if schema.array?
|
|
53
|
+
value = JSON.wrap(value, schema, context: :response).value
|
|
54
|
+
end
|
|
55
|
+
[name.downcase, value]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def [](name)
|
|
60
|
+
@headers[name.downcase]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
##
|
|
65
|
+
# :attr_reader: body
|
|
66
|
+
# The body of the response as Body.
|
|
67
|
+
|
|
68
|
+
##
|
|
69
|
+
# :attr_reader: headers
|
|
70
|
+
# The headers of the response as Headers.
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# :attr_reader: media_type
|
|
74
|
+
# The media type of the response as Media::Type.
|
|
75
|
+
attr_reader :media_type
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# :attr_reader: status_code
|
|
79
|
+
# The status code of the response as Status::Code.
|
|
80
|
+
attr_reader :status_code
|
|
81
|
+
|
|
82
|
+
##
|
|
83
|
+
# :attr_reader: content
|
|
84
|
+
# Same as +body.content+.
|
|
85
|
+
delegate :content, to: :body
|
|
86
|
+
|
|
87
|
+
# :nodoc:
|
|
88
|
+
def initialize(http_response, response_definition)
|
|
89
|
+
@http_response = http_response
|
|
90
|
+
@response_definition = response_definition
|
|
91
|
+
@status_code = Status::Code.from(@http_response.code)
|
|
92
|
+
@media_type = Media::Type.from(@http_response.content_type)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def body
|
|
96
|
+
@body ||= Body.new(
|
|
97
|
+
@http_response.body,
|
|
98
|
+
@media_type,
|
|
99
|
+
@response_definition.content_for(@media_type).schema
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Builds an error message in the form <code>"{status_code}: {message}"</code>.
|
|
104
|
+
def error_message
|
|
105
|
+
message = content
|
|
106
|
+
message = message.message if message.respond_to?(:message)
|
|
107
|
+
|
|
108
|
+
"#{status_code}: #{message}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def headers
|
|
112
|
+
@headers ||= Headers.new(@http_response, @response_definition)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def inspect # :nodoc:
|
|
116
|
+
"#<#{self.class.name} #{@http_response.inspect}>"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jsapi
|
|
4
|
+
module Client
|
|
5
|
+
module Session
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
class TimeoutError < Error; end
|
|
8
|
+
|
|
9
|
+
# \Base session class.
|
|
10
|
+
class Base
|
|
11
|
+
include Test
|
|
12
|
+
|
|
13
|
+
# Creates and starts a new session.
|
|
14
|
+
def initialize(host, port = nil, logger = nil, **options)
|
|
15
|
+
logger = Logger.new(logger) unless logger.nil? || logger.is_a?(Logger)
|
|
16
|
+
@logger = logger
|
|
17
|
+
|
|
18
|
+
@http =
|
|
19
|
+
begin
|
|
20
|
+
Net::HTTP.start(host, port, options)
|
|
21
|
+
rescue StandardError => e
|
|
22
|
+
raise wrap_error(e)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Closes the session.
|
|
27
|
+
def finish
|
|
28
|
+
@http.finish
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
raise wrap_error(e)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Sends +request+.
|
|
34
|
+
def send_request(request)
|
|
35
|
+
@logger&.log_request(request)
|
|
36
|
+
|
|
37
|
+
response =
|
|
38
|
+
begin
|
|
39
|
+
@http.request(request)
|
|
40
|
+
rescue StandardError => e
|
|
41
|
+
raise wrap_error(e)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@logger&.log_response(response)
|
|
45
|
+
response
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def wrap_error(cause)
|
|
51
|
+
case cause
|
|
52
|
+
when Timeout::Error
|
|
53
|
+
TimeoutError
|
|
54
|
+
else
|
|
55
|
+
Error
|
|
56
|
+
end.new(cause: cause)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jsapi
|
|
4
|
+
module Client
|
|
5
|
+
module Session
|
|
6
|
+
class Logger
|
|
7
|
+
def initialize(logger, **options)
|
|
8
|
+
@logger = ActiveSupport::TaggedLogging.new(logger)
|
|
9
|
+
@body = options[:body] == true
|
|
10
|
+
@level = options[:level] || 0
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Logs +http_request+.
|
|
14
|
+
def log_request(request)
|
|
15
|
+
return if @logger.level > @level
|
|
16
|
+
|
|
17
|
+
tagged('REQ') do
|
|
18
|
+
log("#{request.method} #{request.path}")
|
|
19
|
+
log(request.body, truncate_to: 8_192) if body?
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Logs +http_response+.
|
|
24
|
+
def log_response(response)
|
|
25
|
+
return if @logger.level > @level
|
|
26
|
+
|
|
27
|
+
tagged('RES') do
|
|
28
|
+
log("HTTP/#{response.http_version} #{response.code}")
|
|
29
|
+
log(response.body, truncate_to: 8_192) if body?
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def body?
|
|
36
|
+
@body == true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def log(message, truncate_to: nil)
|
|
40
|
+
return if message.nil?
|
|
41
|
+
|
|
42
|
+
message = message.truncate(truncate_to) if truncate_to
|
|
43
|
+
@logger.log(@level, message)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def tagged(*args, &block)
|
|
47
|
+
@logger.tagged('JSAPI CLIENT', *args, &block)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|