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.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jsapi/client/base.rb +182 -0
  3. data/lib/jsapi/client/exceptions.rb +45 -0
  4. data/lib/jsapi/client/request.rb +204 -0
  5. data/lib/jsapi/client/response.rb +120 -0
  6. data/lib/jsapi/client/session/base.rb +61 -0
  7. data/lib/jsapi/client/session/logger.rb +52 -0
  8. data/lib/jsapi/client/session/single_request.rb +16 -0
  9. data/lib/jsapi/client/session/test/class_methods.rb +49 -0
  10. data/lib/jsapi/client/session/test/response.rb +56 -0
  11. data/lib/jsapi/client/session/test/spy.rb +43 -0
  12. data/lib/jsapi/client/session/test.rb +17 -0
  13. data/lib/jsapi/client/session.rb +6 -0
  14. data/lib/jsapi/client.rb +7 -0
  15. data/lib/jsapi/configuration.rb +2 -2
  16. data/lib/jsapi/controller/authentication/credentials/http/bearer.rb +5 -12
  17. data/lib/jsapi/controller/authentication/credentials.rb +4 -4
  18. data/lib/jsapi/controller/authentication.rb +3 -3
  19. data/lib/jsapi/controller/methods.rb +43 -77
  20. data/lib/jsapi/controller/parameters.rb +6 -6
  21. data/lib/jsapi/controller/response.rb +7 -7
  22. data/lib/jsapi/{meta/definitions.rb → definition/api.rb} +62 -40
  23. data/lib/jsapi/{meta → definition}/callable/symbol_evaluator.rb +1 -1
  24. data/lib/jsapi/{meta → definition}/callable/symbol_sequence_evaluator.rb +1 -1
  25. data/lib/jsapi/{meta → definition}/callable.rb +1 -1
  26. data/lib/jsapi/{meta → definition}/callback/base.rb +5 -5
  27. data/lib/jsapi/{meta → definition}/callback/reference.rb +1 -1
  28. data/lib/jsapi/{meta → definition}/callback.rb +1 -1
  29. data/lib/jsapi/{meta → definition}/contact.rb +1 -1
  30. data/lib/jsapi/{meta → definition}/content.rb +11 -11
  31. data/lib/jsapi/{meta → definition}/defaults.rb +1 -1
  32. data/lib/jsapi/{meta → definition}/example/base.rb +1 -1
  33. data/lib/jsapi/{meta → definition}/example/builder.rb +4 -4
  34. data/lib/jsapi/{meta → definition}/example/reference.rb +1 -1
  35. data/lib/jsapi/{meta → definition}/example.rb +1 -1
  36. data/lib/jsapi/{meta → definition}/existence.rb +1 -1
  37. data/lib/jsapi/{meta → definition}/external_documentation.rb +1 -1
  38. data/lib/jsapi/{meta → definition}/header/base.rb +1 -1
  39. data/lib/jsapi/{meta → definition}/header/reference.rb +1 -1
  40. data/lib/jsapi/{meta → definition}/header.rb +1 -1
  41. data/lib/jsapi/{meta → definition}/info.rb +1 -1
  42. data/lib/jsapi/{meta → definition}/license.rb +1 -1
  43. data/lib/jsapi/{meta → definition}/link/base.rb +1 -1
  44. data/lib/jsapi/{meta → definition}/link/reference.rb +1 -1
  45. data/lib/jsapi/{meta → definition}/link.rb +1 -1
  46. data/lib/jsapi/{meta → definition}/model/attributes/class_methods.rb +1 -1
  47. data/lib/jsapi/{meta → definition}/model/attributes/frozen_error.rb +1 -1
  48. data/lib/jsapi/{meta → definition}/model/attributes/type_caster.rb +2 -2
  49. data/lib/jsapi/{meta → definition}/model/attributes.rb +1 -1
  50. data/lib/jsapi/{meta → definition}/model/base.rb +1 -1
  51. data/lib/jsapi/{meta → definition}/model/reference.rb +11 -11
  52. data/lib/jsapi/{meta → definition}/model/wrappable.rb +3 -3
  53. data/lib/jsapi/{meta → definition}/model/wrapper.rb +5 -5
  54. data/lib/jsapi/{meta → definition}/oauth_flow.rb +1 -1
  55. data/lib/jsapi/{meta → definition}/openapi/extensions.rb +1 -1
  56. data/lib/jsapi/{meta → definition}/openapi/path_item.rb +5 -5
  57. data/lib/jsapi/{meta → definition}/openapi/version.rb +2 -2
  58. data/lib/jsapi/{meta → definition}/openapi.rb +1 -1
  59. data/lib/jsapi/{meta → definition}/operation.rb +108 -62
  60. data/lib/jsapi/{meta → definition}/parameter/base.rb +11 -11
  61. data/lib/jsapi/{meta → definition}/parameter/reference.rb +5 -5
  62. data/lib/jsapi/{meta → definition}/parameter/wrapper.rb +2 -2
  63. data/lib/jsapi/{meta → definition}/parameter.rb +1 -1
  64. data/lib/jsapi/{meta → definition}/path.rb +1 -1
  65. data/lib/jsapi/{meta → definition}/pathname.rb +1 -1
  66. data/lib/jsapi/{meta → definition}/property.rb +2 -2
  67. data/lib/jsapi/{meta → definition}/reference_error.rb +1 -1
  68. data/lib/jsapi/{meta → definition}/request_body/base.rb +1 -1
  69. data/lib/jsapi/{meta → definition}/request_body/reference.rb +1 -1
  70. data/lib/jsapi/{meta → definition}/request_body/wrapper.rb +2 -2
  71. data/lib/jsapi/{meta → definition}/request_body.rb +1 -1
  72. data/lib/jsapi/{meta → definition}/rescue_handler.rb +1 -1
  73. data/lib/jsapi/{meta → definition}/response/base.rb +4 -4
  74. data/lib/jsapi/{meta → definition}/response/reference.rb +1 -1
  75. data/lib/jsapi/{meta → definition}/response/wrapper.rb +17 -5
  76. data/lib/jsapi/{meta → definition}/response.rb +1 -1
  77. data/lib/jsapi/{meta → definition}/schema/additional_properties.rb +2 -2
  78. data/lib/jsapi/{meta → definition}/schema/array.rb +2 -2
  79. data/lib/jsapi/{meta → definition}/schema/base.rb +1 -1
  80. data/lib/jsapi/{meta → definition}/schema/boolean.rb +1 -1
  81. data/lib/jsapi/{meta → definition}/schema/boundary.rb +1 -1
  82. data/lib/jsapi/{meta → definition}/schema/conversion.rb +1 -1
  83. data/lib/jsapi/{meta → definition}/schema/discriminator.rb +1 -1
  84. data/lib/jsapi/{meta → definition}/schema/integer.rb +1 -1
  85. data/lib/jsapi/{meta → definition}/schema/number.rb +1 -1
  86. data/lib/jsapi/{meta → definition}/schema/numeric.rb +1 -1
  87. data/lib/jsapi/{meta → definition}/schema/object.rb +13 -13
  88. data/lib/jsapi/{meta → definition}/schema/reference.rb +1 -1
  89. data/lib/jsapi/{meta → definition}/schema/string.rb +1 -1
  90. data/lib/jsapi/{meta → definition}/schema/validation/base.rb +1 -1
  91. data/lib/jsapi/{meta → definition}/schema/validation/enum.rb +1 -1
  92. data/lib/jsapi/{meta → definition}/schema/validation/max_items.rb +1 -1
  93. data/lib/jsapi/{meta → definition}/schema/validation/max_length.rb +1 -1
  94. data/lib/jsapi/{meta → definition}/schema/validation/maximum.rb +1 -1
  95. data/lib/jsapi/{meta → definition}/schema/validation/min_items.rb +1 -1
  96. data/lib/jsapi/{meta → definition}/schema/validation/min_length.rb +1 -1
  97. data/lib/jsapi/{meta → definition}/schema/validation/minimum.rb +1 -1
  98. data/lib/jsapi/{meta → definition}/schema/validation/multiple_of.rb +1 -1
  99. data/lib/jsapi/{meta → definition}/schema/validation/pattern.rb +1 -1
  100. data/lib/jsapi/{meta → definition}/schema/wrapper.rb +18 -4
  101. data/lib/jsapi/{meta → definition}/schema.rb +5 -5
  102. data/lib/jsapi/{meta → definition}/security_requirement.rb +1 -1
  103. data/lib/jsapi/{meta → definition}/security_scheme/api_key.rb +1 -1
  104. data/lib/jsapi/{meta → definition}/security_scheme/base.rb +1 -1
  105. data/lib/jsapi/{meta → definition}/security_scheme/http/basic.rb +1 -1
  106. data/lib/jsapi/{meta → definition}/security_scheme/http/bearer.rb +1 -1
  107. data/lib/jsapi/{meta → definition}/security_scheme/http/other.rb +1 -1
  108. data/lib/jsapi/{meta → definition}/security_scheme/http.rb +1 -1
  109. data/lib/jsapi/{meta → definition}/security_scheme/mutual_tls.rb +1 -1
  110. data/lib/jsapi/{meta → definition}/security_scheme/oauth2.rb +1 -1
  111. data/lib/jsapi/{meta → definition}/security_scheme/open_id_connect.rb +1 -1
  112. data/lib/jsapi/{meta → definition}/security_scheme.rb +1 -1
  113. data/lib/jsapi/{meta → definition}/server.rb +24 -1
  114. data/lib/jsapi/{meta → definition}/server_variable.rb +1 -1
  115. data/lib/jsapi/{meta → definition}/tag.rb +1 -1
  116. data/lib/jsapi/definition.rb +33 -0
  117. data/lib/jsapi/dsl/{definitions.rb → api.rb} +48 -48
  118. data/lib/jsapi/dsl/base.rb +7 -7
  119. data/lib/jsapi/dsl/class_methods.rb +34 -32
  120. data/lib/jsapi/dsl/examples.rb +1 -1
  121. data/lib/jsapi/dsl/operation.rb +9 -7
  122. data/lib/jsapi/dsl/parameter.rb +1 -1
  123. data/lib/jsapi/dsl/path.rb +8 -6
  124. data/lib/jsapi/dsl/response.rb +5 -5
  125. data/lib/jsapi/dsl/schema.rb +17 -17
  126. data/lib/jsapi/dsl/shared_operation_methods.rb +11 -26
  127. data/lib/jsapi/dsl.rb +2 -2
  128. data/lib/jsapi/model/errors.rb +3 -3
  129. data/lib/jsapi/status/code.rb +20 -0
  130. data/lib/jsapi/support/messages.rb +27 -0
  131. data/lib/jsapi/support.rb +3 -0
  132. data/lib/jsapi/version.rb +1 -1
  133. data/lib/jsapi.rb +3 -3
  134. metadata +115 -102
  135. data/lib/jsapi/jsonify_error.rb +0 -17
  136. data/lib/jsapi/messages.rb +0 -19
  137. data/lib/jsapi/meta.rb +0 -38
  138. /data/lib/jsapi/{meta → definition}/model.rb +0 -0
  139. /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: 1f68753b0fe40a759d233878a9ca2a96d227ba769d8e1d167255f48e538eb075
4
- data.tar.gz: e6ba6a151038cf393c099784ce7b3bd358685de018308e3e437af0d54cfcf438
3
+ metadata.gz: 0ec240995632c56faf46229f425a0fb65a32da97f6aa0b68ee66327a6a17ca3b
4
+ data.tar.gz: 7c457efa33d29830814b66dc62ac001d813f042890b7e637e265567198251e01
5
5
  SHA512:
6
- metadata.gz: ef47ff95a63d481af9b7eb169487ad7e05d6a653a5a6d2bdefb8499802a43531e2d729bce9a74701006b59c66f24241d9a9dae7b2a7e0f95e5788e81cc73dacb
7
- data.tar.gz: 9cf392df9e3d8bd4913faa8c3594c3e10399c9600a165e96f8f8ae91c2c240f60e7e77518ac018d5d327d6c3bde4aa9e9119babda7ef0959e285a0c5365741ce
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
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module Client
5
+ module Session
6
+ class SingleRequest < Base
7
+ # Sends +request+ and closes the session.
8
+ def send_request(request)
9
+ super
10
+ ensure
11
+ finish
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end