jsapi 1.1.1 → 1.3

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jsapi/configuration.rb +2 -2
  3. data/lib/jsapi/controller/methods.rb +17 -4
  4. data/lib/jsapi/controller/parameters.rb +27 -10
  5. data/lib/jsapi/controller/response.rb +29 -5
  6. data/lib/jsapi/json/array.rb +4 -0
  7. data/lib/jsapi/json/object.rb +2 -0
  8. data/lib/jsapi/json/value.rb +5 -0
  9. data/lib/jsapi/meta/callback/base.rb +1 -1
  10. data/lib/jsapi/meta/definitions.rb +10 -6
  11. data/lib/jsapi/meta/example/base.rb +4 -6
  12. data/lib/jsapi/meta/link/base.rb +4 -2
  13. data/lib/jsapi/meta/oauth_flow.rb +11 -2
  14. data/lib/jsapi/meta/openapi/path_item.rb +46 -0
  15. data/lib/jsapi/meta/openapi/version.rb +6 -0
  16. data/lib/jsapi/meta/openapi.rb +2 -0
  17. data/lib/jsapi/meta/operation.rb +6 -14
  18. data/lib/jsapi/meta/parameter/base.rb +82 -59
  19. data/lib/jsapi/meta/request_body/base.rb +3 -2
  20. data/lib/jsapi/meta/response/base.rb +26 -8
  21. data/lib/jsapi/meta/schema/base.rb +1 -1
  22. data/lib/jsapi/meta/schema/discriminator.rb +14 -4
  23. data/lib/jsapi/meta/schema/object.rb +28 -9
  24. data/lib/jsapi/meta/schema/validation/maximum.rb +1 -1
  25. data/lib/jsapi/meta/schema/validation/minimum.rb +1 -1
  26. data/lib/jsapi/meta/security_scheme/api_key.rb +4 -5
  27. data/lib/jsapi/meta/security_scheme/base.rb +16 -0
  28. data/lib/jsapi/meta/security_scheme/http/basic.rb +3 -10
  29. data/lib/jsapi/meta/security_scheme/http/bearer.rb +8 -8
  30. data/lib/jsapi/meta/security_scheme/http/other.rb +3 -5
  31. data/lib/jsapi/meta/security_scheme/mutual_tls.rb +23 -0
  32. data/lib/jsapi/meta/security_scheme/oauth2.rb +29 -13
  33. data/lib/jsapi/meta/security_scheme/open_id_connect.rb +5 -8
  34. data/lib/jsapi/meta/security_scheme.rb +3 -0
  35. data/lib/jsapi/meta/server.rb +9 -1
  36. data/lib/jsapi/meta/tag.rb +34 -4
  37. data/lib/jsapi/model/base.rb +37 -1
  38. data/lib/jsapi/model/nestable.rb +27 -1
  39. data/lib/jsapi/version.rb +1 -1
  40. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5106832b9b6b3b6747fc83b4469176532e56a84d2fe1779cdad6e6e34d9da4b
4
- data.tar.gz: 1d760c1fbb5556dfac42203ae05c483f2fcf99e3aa0c6bfc34c374d7093fbd0c
3
+ metadata.gz: 2abec73a53f67bfebe51e0466e662be1dfc393f3fe5946612a12c94fde8b463b
4
+ data.tar.gz: f9c69c6df40d4f88e5175beb185a528ad76f2c6b4cbf6a8f88ac15915943f706
5
5
  SHA512:
6
- metadata.gz: 3261a33d232a31b42f334d0ef13e36d5e0dd92c2863948fdcb54dee791e6580a2adf26c7fe1b0685837fb01d69cf3dc1b6f6f25557840c10c6ade65725ee6aaf
7
- data.tar.gz: baaf0e9710ac6909ff970534f758a539835807d0e61515440759245960aac83e9477c231e42f48c03822a10e04afaaef19fbf3320972c303821f2e23aa4e4bfb
6
+ metadata.gz: ca369ab50a38016fa37e9199e5a38f9248d2cbcfa51d80c39461dd206d8b09fb6ae60ea29861d211b9a939e408b6e48f35008da577c9f3a789bb6ebb592fafda
7
+ data.tar.gz: 8093446b1d7601d9a0e89cae395fa0ced837901b1a9eb0866ebcfd721cebb9e07d611c851735c847a699d71cec4ca474d04e39710ebc244476f6deb635e7fdc7
@@ -4,11 +4,11 @@ module Jsapi
4
4
  # Holds the \Jsapi configuration.
5
5
  class Configuration
6
6
  # The path where the API definitions are located relative to +Rails.root+.
7
- # The default is <code>"app/api_defs"</code>.
7
+ # The default is <code>"jsapi/api_defs"</code>.
8
8
  attr_accessor :api_defs_path
9
9
 
10
10
  def initialize # :nodoc:
11
- @api_defs_path = 'app/api_defs'
11
+ @api_defs_path = 'jsapi/api_defs'
12
12
  end
13
13
 
14
14
  # Returns the absolute +Pathname+ for +args+ within +api_defs_path+.
@@ -15,7 +15,9 @@ module Jsapi
15
15
  # Performs an API operation by calling the given block. The request parameters are
16
16
  # passed as an instance of the operation's model class to the block. The object
17
17
  # returned by the block is implicitly rendered according to the appropriate +response+
18
- # specification when the content type is a JSON MIME type.
18
+ # specification when the content type is a JSON MIME type. When content type is
19
+ # <code>application/json-seq</code>, the object returned by the block is streamed in
20
+ # JSON sequence text format.
19
21
  #
20
22
  # api_operation('foo') do |api_params|
21
23
  # # ...
@@ -153,18 +155,29 @@ module Jsapi
153
155
  end
154
156
 
155
157
  # Write response
156
- return unless response_model.json_type?
158
+ return unless response_model.json_type? || response_model.json_seq_type?
157
159
 
158
160
  response = Response.new(result, response_model, definitions, omit: omit)
159
161
  self.content_type = response_model.content_type
160
- render(json: response, status: status)
162
+
163
+ if response_model.json_seq_type?
164
+ self.response.status = status
165
+
166
+ self.response.stream.tap do |stream|
167
+ response.write_json_seq_to(stream)
168
+ ensure
169
+ stream.close
170
+ end
171
+ else
172
+ render(json: response, status: status)
173
+ end
161
174
  end
162
175
 
163
176
  def _api_params(operation, definitions, strong:)
164
177
  (operation.model || Model::Base).new(
165
178
  Parameters.new(
166
179
  params.except(:action, :controller, :format).permit!,
167
- request.headers,
180
+ request,
168
181
  operation,
169
182
  definitions,
170
183
  strong: strong
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cgi'
4
+
3
5
  module Jsapi
4
6
  module Controller
5
7
  # Used to wrap request parameters.
@@ -14,18 +16,32 @@ module Jsapi
14
16
  # If +strong+ is true+ parameters that can be mapped are accepted only. That means that
15
17
  # the instance created is invalid if +params+ contains any parameters that can't be
16
18
  # mapped to a parameter or a request body property of +operation+.
17
- def initialize(params, headers, operation, definitions, strong: false)
18
- @params = params.to_h
19
- @strong = strong == true
19
+ def initialize(params, request, operation, definitions, strong: false)
20
+ params = params.to_h
21
+ unassigned_params = params.dup
22
+
23
+ @params_to_be_validated = strong == true ? params.dup : {}
20
24
  @raw_attributes = {}
21
- @raw_additional_attributes = {}
22
25
 
23
26
  # Parameters
24
27
  operation.parameters.each do |name, parameter_model|
25
28
  parameter_model = parameter_model.resolve(definitions)
26
29
 
27
30
  @raw_attributes[name] = JSON.wrap(
28
- parameter_model.in == 'header' ? headers[name] : @params[name],
31
+ case parameter_model.in
32
+ when 'header'
33
+ request.headers[name]
34
+ when 'querystring'
35
+ query_params = request.query_parameters
36
+ keys = query_params.keys
37
+
38
+ unassigned_params.except!(*keys)
39
+ @params_to_be_validated.except!(*keys)
40
+
41
+ parameter_model.object? ? params.slice(*keys) : query_params.to_query
42
+ else
43
+ unassigned_params.delete(name)
44
+ end,
29
45
  parameter_model.schema.resolve(definitions),
30
46
  definitions,
31
47
  context: :request
@@ -37,23 +53,24 @@ module Jsapi
37
53
  &.schema&.resolve(definitions)
38
54
  if request_body_schema&.object?
39
55
  request_body = JSON.wrap(
40
- @params.except(*operation.parameters.keys),
56
+ unassigned_params,
41
57
  request_body_schema,
42
58
  definitions,
43
59
  context: :request
44
60
  )
45
61
  @raw_attributes.merge!(request_body.raw_attributes)
46
62
  @raw_additional_attributes = request_body.raw_additional_attributes
63
+ @params_to_be_validated.except!(*@raw_additional_attributes.keys)
64
+ else
65
+ @raw_additional_attributes = {}
47
66
  end
48
67
  end
49
68
 
50
69
  # Validates the request parameters. Returns true if the parameters are valid, false
51
70
  # otherwise. Detected errors are added to +errors+.
52
71
  def validate(errors)
53
- [
54
- validate_attributes(errors),
55
- !@strong || validate_parameters(@params, attributes, errors)
56
- ].all?
72
+ validate_attributes(errors) &&
73
+ validate_parameters(@params_to_be_validated, attributes, errors)
57
74
  end
58
75
 
59
76
  private
@@ -50,11 +50,27 @@ module Jsapi
50
50
  # Returns the \JSON representation of the response as a string.
51
51
  def to_json(*)
52
52
  schema = @response.schema.resolve(@definitions)
53
- if @response.locale
54
- I18n.with_locale(@response.locale) { jsonify(@object, schema) }
55
- else
56
- jsonify(@object, schema)
57
- end.to_json
53
+ with_locale { jsonify(@object, schema) }.to_json
54
+ end
55
+
56
+ # Writes the response in \JSON sequence text format to +stream+.
57
+ def write_json_seq_to(stream)
58
+ schema = @response.schema.resolve(@definitions)
59
+ with_locale do
60
+ items, item_schema =
61
+ if schema.array? && @object.respond_to?(:each)
62
+ [@object, schema.items.resolve(@definitions)]
63
+ else
64
+ [[@object], schema]
65
+ end
66
+
67
+ items.each do |item|
68
+ stream.write("\u001E") # Record separator (see RFC 7464)
69
+ stream.write(jsonify(item, item_schema).to_json)
70
+ stream.write("\n")
71
+ end
72
+ end
73
+ nil
58
74
  end
59
75
 
60
76
  private
@@ -138,6 +154,14 @@ module Jsapi
138
154
 
139
155
  properties.presence
140
156
  end
157
+
158
+ def with_locale(&block)
159
+ if @response.locale
160
+ I18n.with_locale(@response.locale, &block)
161
+ else
162
+ yield
163
+ end
164
+ end
141
165
  end
142
166
  end
143
167
  end
@@ -20,6 +20,10 @@ module Jsapi
20
20
  "#<#{self.class.name} [#{@json_values.map(&:inspect).join(', ')}]>"
21
21
  end
22
22
 
23
+ def serializable_value(**options) # :nodoc:
24
+ @json_values.map { |element| element.serializable_value(**options) }
25
+ end
26
+
23
27
  def validate(errors) # :nodoc:
24
28
  return false unless super
25
29
 
@@ -6,6 +6,8 @@ module Jsapi
6
6
  class Object < Value
7
7
  include Model::Nestable
8
8
 
9
+ alias serializable_value serializable_hash
10
+
9
11
  attr_reader :raw_additional_attributes, :raw_attributes
10
12
 
11
13
  def initialize(hash, schema, definitions, context: nil)
@@ -29,6 +29,11 @@ module Jsapi
29
29
  false
30
30
  end
31
31
 
32
+ # Returns a serializable representation of +value+.
33
+ def serializable_value(**options)
34
+ options[:jsonify_values] == true ? value.as_json : value
35
+ end
36
+
32
37
  # Validates it against #schema. Returns true if it is valid, false otherwise.
33
38
  # Detected errors are added to +errors+.
34
39
  def validate(errors)
@@ -23,7 +23,7 @@ module Jsapi
23
23
  # Returns a hash representing the \OpenAPI callback object.
24
24
  def to_openapi(version, definitions)
25
25
  operations.transform_values do |operation|
26
- { operation.method => operation.to_openapi(version, definitions) }
26
+ OpenAPI::PathItem.new([operation]).to_openapi(version, definitions)
27
27
  end
28
28
  end
29
29
  end
@@ -231,9 +231,7 @@ module Jsapi
231
231
  openapi_paths =
232
232
  operations.group_by { |operation| operation.path || default_operation_path }
233
233
  .transform_values do |operations_by_path|
234
- operations_by_path.index_by(&:method).transform_values do |operation|
235
- operation.to_openapi(version, self)
236
- end
234
+ OpenAPI::PathItem.new(operations_by_path).to_openapi(version, self)
237
235
  end.presence
238
236
 
239
237
  openapi_objects =
@@ -271,10 +269,16 @@ module Jsapi
271
269
  else
272
270
  {
273
271
  # Order according to the OpenAPI specification 3.x
274
- openapi: version.minor.zero? ? '3.0.3' : '3.1.1',
272
+ openapi:
273
+ case version.minor
274
+ when 0 then '3.0.3'
275
+ when 1 then '3.1.1'
276
+ when 2 then '3.2.0'
277
+ end,
275
278
  info: openapi_objects[:info],
276
- servers: openapi_objects[:servers] ||
277
- [default_server&.to_openapi].compact.presence,
279
+ servers:
280
+ openapi_objects[:servers] ||
281
+ [default_server&.to_openapi(version)].compact.presence,
278
282
  paths: openapi_paths,
279
283
  components: {
280
284
  schemas: openapi_objects[:schemas],
@@ -29,13 +29,11 @@ module Jsapi
29
29
 
30
30
  # Returns a hash representing the \OpenAPI example object.
31
31
  def to_openapi(*)
32
- with_openapi_extensions(summary: summary, description: description).tap do |result|
33
- if external?
34
- result[:external_value] = value
35
- else
36
- result[:value] = value
32
+ with_openapi_extensions(
33
+ { summary: summary, description: description }.tap do |result|
34
+ result[external? ? :externalValue : :value] = value
37
35
  end
38
- end
36
+ )
39
37
  end
40
38
  end
41
39
  end
@@ -33,13 +33,15 @@ module Jsapi
33
33
  attribute :server, Server
34
34
 
35
35
  # Returns a hash representing the \OpenAPI link object.
36
- def to_openapi(*)
36
+ def to_openapi(version, *)
37
+ version = OpenAPI::Version.from(version)
38
+
37
39
  with_openapi_extensions(
38
40
  operationId: operation_id,
39
41
  parameters: parameters.presence,
40
42
  requestBody: request_body,
41
43
  description: description,
42
- server: server&.to_openapi
44
+ server: server&.to_openapi(version)
43
45
  )
44
46
  end
45
47
  end
@@ -18,12 +18,20 @@ module Jsapi
18
18
  # The authorization URL to be used for the flow.
19
19
  attribute :authorization_url, String
20
20
 
21
+ ##
22
+ # :attr: device_authorization_url
23
+ # The device authorization URL to be used for the flow.
24
+ #
25
+ # Note that the device authorization URL was introduced with \OpenAPI 3.2.
26
+ # It is omitted when generating an \OpenAPI document with a lower version.
27
+ attribute :device_authorization_url, String
28
+
21
29
  ##
22
30
  # :attr: refresh_url
23
31
  # The refresh URL to be used for the flow.
24
32
  #
25
33
  # Note that the refresh URL was introduced with \OpenAPI 3.0. It is
26
- # skipped when generating an \OpenAPI 2.0 document.
34
+ # omitted when generating an \OpenAPI document with a lower version.
27
35
  attribute :refresh_url, String
28
36
 
29
37
  ##
@@ -42,8 +50,9 @@ module Jsapi
42
50
 
43
51
  with_openapi_extensions(
44
52
  authorizationUrl: authorization_url,
53
+ deviceAuthorizationUrl: (device_authorization_url if version >= OpenAPI::V3_2),
45
54
  tokenUrl: token_url,
46
- refreshUrl: (refresh_url if version.major > 2),
55
+ refreshUrl: (refresh_url if version >= OpenAPI::V3_0),
47
56
  scopes: scopes.transform_values(&:description)
48
57
  )
49
58
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module Meta
5
+ module OpenAPI
6
+ class PathItem # :nodoc:
7
+ def initialize(operations)
8
+ @operations = operations
9
+ end
10
+
11
+ def to_openapi(version, definitions)
12
+ version = OpenAPI::Version.from(version)
13
+
14
+ {}.tap do |fields|
15
+ @operations.each do |operation|
16
+ method = operation.method
17
+ standardized_method = method.downcase
18
+
19
+ if standard_method?(standardized_method, version)
20
+ fields[standardized_method] = operation.to_openapi(version, definitions)
21
+ elsif version >= OpenAPI::V3_2
22
+ additional_operations = fields[:additionalOperations] ||= {}
23
+ additional_operations[method] = operation.to_openapi(version, definitions)
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def standard_method?(method, version)
32
+ case method
33
+ when 'delete', 'get', 'head', 'options', 'patch', 'post', 'put'
34
+ true
35
+ when 'trace'
36
+ version >= OpenAPI::V3_0
37
+ when 'query'
38
+ version >= OpenAPI::V3_2
39
+ else
40
+ false
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -19,6 +19,8 @@ module Jsapi
19
19
  new(3, 0)
20
20
  when '3.1'
21
21
  new(3, 1)
22
+ when '3.2'
23
+ new(3, 2)
22
24
  else
23
25
  raise ArgumentError, "unsupported OpenAPI version: #{version.inspect}"
24
26
  end
@@ -46,6 +48,10 @@ module Jsapi
46
48
  minor <=> other.minor
47
49
  end
48
50
 
51
+ def inspect
52
+ "<#{self.class.name} #{self}>"
53
+ end
54
+
49
55
  def to_s # :nodoc:
50
56
  "#{major}.#{minor}"
51
57
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'openapi/extensions'
4
4
  require_relative 'openapi/version'
5
+ require_relative 'openapi/path_item'
5
6
 
6
7
  module Jsapi
7
8
  module Meta
@@ -9,6 +10,7 @@ module Jsapi
9
10
  V2_0 = Version.new(2, 0)
10
11
  V3_0 = Version.new(3, 0)
11
12
  V3_1 = Version.new(3, 1)
13
+ V3_2 = Version.new(3, 2)
12
14
  end
13
15
  end
14
16
  end
@@ -28,18 +28,8 @@ module Jsapi
28
28
 
29
29
  ##
30
30
  # :attr: method
31
- # The HTTP verb of the operation. Possible values are:
32
- #
33
- # - <code>"delete"</code>
34
- # - <code>"get"</code>
35
- # - <code>"head"</code>
36
- # - <code>"options"</code>
37
- # - <code>"patch"</code>
38
- # - <code>"post"</code>
39
- # - <code>"put"</code>
40
- #
41
- # The default HTTP verb is <code>"get"</code>.
42
- attribute :method, values: %w[delete get head options patch post put], default: 'get'
31
+ # The HTTP method of the operation, <code>"get"</code> by default.
32
+ attribute :method, String, default: 'get'
43
33
 
44
34
  ##
45
35
  # :attr: model
@@ -97,7 +87,7 @@ module Jsapi
97
87
 
98
88
  ##
99
89
  # :attr: summary
100
- # The short summary of the operation.
90
+ # The short description of the operation.
101
91
  attribute :summary, String
102
92
 
103
93
  ##
@@ -148,7 +138,9 @@ module Jsapi
148
138
  end
149
139
  result[:schemes] = schemes if schemes.present?
150
140
  elsif servers.present?
151
- result[:servers] = servers.map(&:to_openapi)
141
+ result[:servers] = servers.map do |server|
142
+ server.to_openapi(version)
143
+ end
152
144
  end
153
145
  # Parameters (and request body)
154
146
  result[:parameters] = parameters.values.flat_map do |parameter|