jsapi 0.1.1
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 +7 -0
- data/lib/jsapi/controller/base.rb +21 -0
- data/lib/jsapi/controller/error_result.rb +21 -0
- data/lib/jsapi/controller/methods.rb +144 -0
- data/lib/jsapi/controller/parameters.rb +86 -0
- data/lib/jsapi/controller/parameters_invalid.rb +25 -0
- data/lib/jsapi/controller/response.rb +84 -0
- data/lib/jsapi/controller.rb +13 -0
- data/lib/jsapi/dsl/callbacks.rb +32 -0
- data/lib/jsapi/dsl/class_methods.rb +85 -0
- data/lib/jsapi/dsl/definitions.rb +102 -0
- data/lib/jsapi/dsl/error.rb +38 -0
- data/lib/jsapi/dsl/examples.rb +30 -0
- data/lib/jsapi/dsl/node.rb +62 -0
- data/lib/jsapi/dsl/openapi/callback.rb +23 -0
- data/lib/jsapi/dsl/openapi/root.rb +12 -0
- data/lib/jsapi/dsl/openapi.rb +4 -0
- data/lib/jsapi/dsl/operation.rb +118 -0
- data/lib/jsapi/dsl/parameter.rb +10 -0
- data/lib/jsapi/dsl/request_body.rb +10 -0
- data/lib/jsapi/dsl/response.rb +33 -0
- data/lib/jsapi/dsl/schema.rb +87 -0
- data/lib/jsapi/dsl.rb +24 -0
- data/lib/jsapi/json/array.rb +35 -0
- data/lib/jsapi/json/boolean.rb +17 -0
- data/lib/jsapi/json/integer.rb +15 -0
- data/lib/jsapi/json/null.rb +27 -0
- data/lib/jsapi/json/number.rb +15 -0
- data/lib/jsapi/json/object.rb +53 -0
- data/lib/jsapi/json/string.rb +29 -0
- data/lib/jsapi/json/value.rb +47 -0
- data/lib/jsapi/json.rb +41 -0
- data/lib/jsapi/meta/attributes/class_methods.rb +112 -0
- data/lib/jsapi/meta/attributes/type_caster.rb +48 -0
- data/lib/jsapi/meta/attributes.rb +4 -0
- data/lib/jsapi/meta/base.rb +41 -0
- data/lib/jsapi/meta/base_reference.rb +33 -0
- data/lib/jsapi/meta/definitions.rb +226 -0
- data/lib/jsapi/meta/example/model.rb +44 -0
- data/lib/jsapi/meta/example/reference.rb +15 -0
- data/lib/jsapi/meta/example.rb +19 -0
- data/lib/jsapi/meta/existence.rb +69 -0
- data/lib/jsapi/meta/invalid_argument_error.rb +11 -0
- data/lib/jsapi/meta/openapi/callback/model.rb +36 -0
- data/lib/jsapi/meta/openapi/callback/reference.rb +16 -0
- data/lib/jsapi/meta/openapi/callback.rb +21 -0
- data/lib/jsapi/meta/openapi/contact.rb +34 -0
- data/lib/jsapi/meta/openapi/external_documentation.rb +28 -0
- data/lib/jsapi/meta/openapi/info.rb +52 -0
- data/lib/jsapi/meta/openapi/license.rb +28 -0
- data/lib/jsapi/meta/openapi/link/model.rb +48 -0
- data/lib/jsapi/meta/openapi/link/reference.rb +16 -0
- data/lib/jsapi/meta/openapi/link.rb +21 -0
- data/lib/jsapi/meta/openapi/oauth_flow.rb +50 -0
- data/lib/jsapi/meta/openapi/root.rb +134 -0
- data/lib/jsapi/meta/openapi/security_requirement.rb +27 -0
- data/lib/jsapi/meta/openapi/security_scheme/api_key.rb +38 -0
- data/lib/jsapi/meta/openapi/security_scheme/base.rb +16 -0
- data/lib/jsapi/meta/openapi/security_scheme/http/basic.rb +31 -0
- data/lib/jsapi/meta/openapi/security_scheme/http/bearer.rb +37 -0
- data/lib/jsapi/meta/openapi/security_scheme/http/other.rb +37 -0
- data/lib/jsapi/meta/openapi/security_scheme/http.rb +31 -0
- data/lib/jsapi/meta/openapi/security_scheme/oauth2.rb +47 -0
- data/lib/jsapi/meta/openapi/security_scheme/open_id_connect.rb +33 -0
- data/lib/jsapi/meta/openapi/security_scheme.rb +51 -0
- data/lib/jsapi/meta/openapi/server.rb +34 -0
- data/lib/jsapi/meta/openapi/server_variable.rb +34 -0
- data/lib/jsapi/meta/openapi/tag.rb +34 -0
- data/lib/jsapi/meta/openapi/version.rb +41 -0
- data/lib/jsapi/meta/openapi.rb +16 -0
- data/lib/jsapi/meta/operation.rb +186 -0
- data/lib/jsapi/meta/parameter/model.rb +170 -0
- data/lib/jsapi/meta/parameter/reference.rb +30 -0
- data/lib/jsapi/meta/parameter.rb +19 -0
- data/lib/jsapi/meta/property.rb +62 -0
- data/lib/jsapi/meta/reference_error.rb +12 -0
- data/lib/jsapi/meta/request_body/model.rb +65 -0
- data/lib/jsapi/meta/request_body/reference.rb +14 -0
- data/lib/jsapi/meta/request_body.rb +19 -0
- data/lib/jsapi/meta/rescue_handler.rb +26 -0
- data/lib/jsapi/meta/response/model.rb +72 -0
- data/lib/jsapi/meta/response/reference.rb +17 -0
- data/lib/jsapi/meta/response.rb +19 -0
- data/lib/jsapi/meta/schema/array.rb +42 -0
- data/lib/jsapi/meta/schema/base.rb +146 -0
- data/lib/jsapi/meta/schema/boolean.rb +9 -0
- data/lib/jsapi/meta/schema/boundary.rb +37 -0
- data/lib/jsapi/meta/schema/conversion.rb +28 -0
- data/lib/jsapi/meta/schema/delegator.rb +26 -0
- data/lib/jsapi/meta/schema/discriminator.rb +36 -0
- data/lib/jsapi/meta/schema/integer.rb +9 -0
- data/lib/jsapi/meta/schema/number.rb +9 -0
- data/lib/jsapi/meta/schema/numeric.rb +56 -0
- data/lib/jsapi/meta/schema/object.rb +85 -0
- data/lib/jsapi/meta/schema/reference.rb +38 -0
- data/lib/jsapi/meta/schema/string.rb +58 -0
- data/lib/jsapi/meta/schema/validation/base.rb +29 -0
- data/lib/jsapi/meta/schema/validation/enum.rb +26 -0
- data/lib/jsapi/meta/schema/validation/max_items.rb +26 -0
- data/lib/jsapi/meta/schema/validation/max_length.rb +26 -0
- data/lib/jsapi/meta/schema/validation/maximum.rb +51 -0
- data/lib/jsapi/meta/schema/validation/min_items.rb +26 -0
- data/lib/jsapi/meta/schema/validation/min_length.rb +26 -0
- data/lib/jsapi/meta/schema/validation/minimum.rb +51 -0
- data/lib/jsapi/meta/schema/validation/multiple_of.rb +24 -0
- data/lib/jsapi/meta/schema/validation/pattern.rb +30 -0
- data/lib/jsapi/meta/schema/validation.rb +12 -0
- data/lib/jsapi/meta/schema.rb +61 -0
- data/lib/jsapi/meta.rb +23 -0
- data/lib/jsapi/model/attributes.rb +22 -0
- data/lib/jsapi/model/base.rb +34 -0
- data/lib/jsapi/model/error.rb +15 -0
- data/lib/jsapi/model/errors.rb +51 -0
- data/lib/jsapi/model/naming.rb +28 -0
- data/lib/jsapi/model/nestable.rb +37 -0
- data/lib/jsapi/model/nested_error.rb +54 -0
- data/lib/jsapi/model/validations.rb +27 -0
- data/lib/jsapi/model.rb +15 -0
- data/lib/jsapi/version.rb +8 -0
- data/lib/jsapi.rb +8 -0
- metadata +162 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4326454795353f4fc3d2513d4b58b5d42df113665fb3e7b197e2aac0917de794
|
4
|
+
data.tar.gz: 3f25ce60031f26bb76b3a4dbebd7cf9cd333ae928bc59d12aee46d215206dcc2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1f0304f56a303c1e2658ddaf31a40c0e3d6ad4584094896cc6eeb9fb40c919e91f45fffa9bc47a0f8096426eadcd301987de313f5145b779974d874f89988a4e
|
7
|
+
data.tar.gz: b2255d64dc7c9e8b05c639a2e62fbc7e33dc71074d3284aedd5788de4da8951c9aebc00c8589a4240bcf450f17ba8090696e96ea5a1259e3c81004fc3b2e9776
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jsapi
|
4
|
+
module Controller
|
5
|
+
# The base API controller class.
|
6
|
+
#
|
7
|
+
# class FooController < Jsapi::Controller::Base
|
8
|
+
# api_operation do
|
9
|
+
# response type: 'string'
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# def index
|
13
|
+
# api_operation { 'Hello world' }
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
class Base < ActionController::API
|
17
|
+
include DSL
|
18
|
+
include Methods
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jsapi
|
4
|
+
module Controller
|
5
|
+
# Used by +api_operation!+ to produce an error response.
|
6
|
+
class ErrorResult
|
7
|
+
|
8
|
+
# The HTTP status code of the error response to be produced.
|
9
|
+
attr_reader :status
|
10
|
+
|
11
|
+
delegate :message, :to_s, to: :@exception
|
12
|
+
|
13
|
+
# Creates a new instance to produce an error response with the given
|
14
|
+
# HTTP status code.
|
15
|
+
def initialize(exception, status:)
|
16
|
+
@exception = exception
|
17
|
+
@status = status
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jsapi
|
4
|
+
module Controller
|
5
|
+
module Methods
|
6
|
+
# Returns the Meta::Definitions instance associated with the controller class.
|
7
|
+
# This method can be used to create an OpenAPI document, for example:
|
8
|
+
#
|
9
|
+
# render(json: api_definitions.openapi_document)
|
10
|
+
#
|
11
|
+
def api_definitions
|
12
|
+
self.class.api_definitions
|
13
|
+
end
|
14
|
+
|
15
|
+
# Performs an API operation by calling the given block. The request parameters
|
16
|
+
# are passed as an instance of the operation's model class to the block. The
|
17
|
+
# object returned by the block is implicitly rendered according to the
|
18
|
+
# appropriate +response+ specification.
|
19
|
+
#
|
20
|
+
# api_operation('foo') do |api_params|
|
21
|
+
# # ...
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# +operation_name+ can be +nil+ if the controller handles one operation only.
|
25
|
+
#
|
26
|
+
# If +strong+ is +true+, parameters that can be mapped are accepted only.
|
27
|
+
# That means that the model passed to the block is invalid if there are any
|
28
|
+
# request parameters that cannot be mapped to a parameter or a request body
|
29
|
+
# property of the operation.
|
30
|
+
#
|
31
|
+
def api_operation(operation_name = nil, status: nil, strong: false, &block)
|
32
|
+
_perform_api_operation(
|
33
|
+
operation_name, false,
|
34
|
+
status: status,
|
35
|
+
strong: strong,
|
36
|
+
&block
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Like +api_operation+, except that a ParametersInvalid exception is raised
|
41
|
+
# if the request parameters are invalid.
|
42
|
+
#
|
43
|
+
# api_operation!('foo') do |api_params|
|
44
|
+
# # ...
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
def api_operation!(operation_name = nil, status: nil, strong: false, &block)
|
48
|
+
_perform_api_operation(
|
49
|
+
operation_name, true,
|
50
|
+
status: status,
|
51
|
+
strong: strong,
|
52
|
+
&block
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the request parameters as an instance of the operation's model class.
|
57
|
+
#
|
58
|
+
# params = api_params('foo')
|
59
|
+
#
|
60
|
+
# +operation_name+ can be +nil+ if the controller handles one operation only.
|
61
|
+
#
|
62
|
+
# If +strong+ is +true+, parameters that can be mapped are accepted only.
|
63
|
+
# That means that the model returned is invalid if there are any request
|
64
|
+
# parameters that cannot be mapped to a parameter or a request body property
|
65
|
+
# of the operation.
|
66
|
+
#
|
67
|
+
# Note that each call of +api_params+ returns a newly created instance.
|
68
|
+
#
|
69
|
+
def api_params(operation_name = nil, strong: false)
|
70
|
+
definitions = api_definitions
|
71
|
+
_api_params(
|
72
|
+
_api_operation(operation_name, definitions),
|
73
|
+
definitions,
|
74
|
+
strong: strong
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns a Response to serialize the JSON representation of +result+ according
|
79
|
+
# to the appropriate +response+ specification.
|
80
|
+
#
|
81
|
+
# render(json: api_response(bar, 'foo', status: 200))
|
82
|
+
#
|
83
|
+
# +operation_name+ can be +nil+ if the controller handles one operation only.
|
84
|
+
def api_response(result, operation_name = nil, status: nil)
|
85
|
+
definitions = api_definitions
|
86
|
+
operation = _api_operation(operation_name, definitions)
|
87
|
+
response = _api_response(operation, status, definitions)
|
88
|
+
|
89
|
+
Response.new(result, response, api_definitions)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def _api_operation(operation_name, definitions)
|
95
|
+
operation = definitions.operation(operation_name)
|
96
|
+
return operation if operation
|
97
|
+
|
98
|
+
raise "operation not defined: #{operation_name}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def _api_params(operation, definitions, strong:)
|
102
|
+
(operation.model || Model::Base).new(
|
103
|
+
Parameters.new(params, operation, definitions, strong: strong)
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
def _api_response(operation, status, definitions)
|
108
|
+
response = operation.response(status)
|
109
|
+
return response.resolve(definitions) if response
|
110
|
+
|
111
|
+
raise "status code not defined: #{status}"
|
112
|
+
end
|
113
|
+
|
114
|
+
def _perform_api_operation(operation_name, bang, status:, strong:, &block)
|
115
|
+
definitions = api_definitions
|
116
|
+
operation = _api_operation(operation_name, definitions)
|
117
|
+
response = _api_response(operation, status, definitions)
|
118
|
+
|
119
|
+
if block
|
120
|
+
params = _api_params(operation, definitions, strong: strong)
|
121
|
+
result = begin
|
122
|
+
raise ParametersInvalid.new(params) if bang && params.invalid?
|
123
|
+
|
124
|
+
block.call(params)
|
125
|
+
rescue StandardError => e
|
126
|
+
# Lookup a rescue handler
|
127
|
+
rescue_handler = definitions.rescue_handler_for(e)
|
128
|
+
raise e if rescue_handler.nil?
|
129
|
+
|
130
|
+
# Change the HTTP status code and response schema
|
131
|
+
status = rescue_handler.status
|
132
|
+
response = operation.response(status)&.resolve(definitions)
|
133
|
+
raise e if response.nil?
|
134
|
+
|
135
|
+
ErrorResult.new(e, status: status)
|
136
|
+
end
|
137
|
+
render(json: Response.new(result, response, definitions), status: status)
|
138
|
+
else
|
139
|
+
head(status)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jsapi
|
4
|
+
module Controller
|
5
|
+
# Used to wrap request parameters.
|
6
|
+
class Parameters
|
7
|
+
include Model::Nestable
|
8
|
+
|
9
|
+
attr_reader :raw_attributes
|
10
|
+
|
11
|
+
# Creates a new instance that wraps +params+ according to +operation+.
|
12
|
+
# References are resolved to API components in +definitions+.
|
13
|
+
#
|
14
|
+
# If +strong+ is true+ parameters that can be mapped are accepted only.
|
15
|
+
# That means that the instance created is invalid if +params+ contains
|
16
|
+
# any parameters that cannot be mapped to a parameter or a request body
|
17
|
+
# property of +operation+.
|
18
|
+
def initialize(params, operation, definitions, strong: false)
|
19
|
+
@params = params
|
20
|
+
@strong = strong == true
|
21
|
+
@raw_attributes = {}
|
22
|
+
|
23
|
+
# Merge parameters and request body properties
|
24
|
+
meta_models = operation.parameters.transform_values do |parameter|
|
25
|
+
parameter.resolve(definitions)
|
26
|
+
end
|
27
|
+
request_body = operation.request_body&.resolve(definitions)
|
28
|
+
if request_body && request_body.schema.respond_to?(:properties)
|
29
|
+
meta_models.merge!(
|
30
|
+
request_body.schema.resolve_properties(:write, definitions)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Wrap params
|
35
|
+
meta_models.each do |name, meta_model|
|
36
|
+
@raw_attributes[name] =
|
37
|
+
JSON.wrap(params[name], meta_model.schema, definitions)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect # :nodoc:
|
42
|
+
"#<#{self.class.name} " \
|
43
|
+
"#{attributes.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')}>"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Validates the request parameters. Returns true if the parameters are
|
47
|
+
# valid, false otherwise. Detected errors are added to +errors+.
|
48
|
+
def validate(errors)
|
49
|
+
[
|
50
|
+
validate_attributes(errors),
|
51
|
+
!@strong || validate_parameters(
|
52
|
+
@params.except(:controller, :action),
|
53
|
+
attributes,
|
54
|
+
errors
|
55
|
+
)
|
56
|
+
].all?
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def validate_parameters(params, attributes, errors, path = [])
|
62
|
+
params.each.map do |key, value|
|
63
|
+
if attributes.key?(key)
|
64
|
+
# Validate nested parameters
|
65
|
+
!value.respond_to?(:keys) || validate_parameters(
|
66
|
+
value,
|
67
|
+
attributes[key].try(:attributes) || {},
|
68
|
+
errors,
|
69
|
+
path + [key]
|
70
|
+
)
|
71
|
+
else
|
72
|
+
errors.add(
|
73
|
+
:base,
|
74
|
+
I18n.translate(
|
75
|
+
'jsapi.errors.forbidden',
|
76
|
+
default: "'%<name>s' isn't allowed",
|
77
|
+
name: path.empty? ? key : (path + [key]).join('.')
|
78
|
+
)
|
79
|
+
)
|
80
|
+
false
|
81
|
+
end
|
82
|
+
end.all?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jsapi
|
4
|
+
module Controller
|
5
|
+
# Raised by Methods#api_operation! if the request parameters are invalid.
|
6
|
+
class ParametersInvalid < StandardError
|
7
|
+
attr_reader :params
|
8
|
+
|
9
|
+
def initialize(params)
|
10
|
+
@params = params
|
11
|
+
super('')
|
12
|
+
end
|
13
|
+
|
14
|
+
# Overrides <code>StandardError#message</code> to lazily generate the
|
15
|
+
# error message.
|
16
|
+
def message
|
17
|
+
"#{
|
18
|
+
@params.errors.full_messages.map do |message|
|
19
|
+
message.delete_suffix('.')
|
20
|
+
end.join('. ')
|
21
|
+
}."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jsapi
|
4
|
+
module Controller
|
5
|
+
# Used to serialize a response.
|
6
|
+
class Response
|
7
|
+
|
8
|
+
# Creates a new instance to serialize +object+ according to +response+.
|
9
|
+
# References are resolved to API components in +definitions+.
|
10
|
+
def initialize(object, response, definitions)
|
11
|
+
@object = object
|
12
|
+
@response = response
|
13
|
+
@definitions = definitions
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect # :nodoc:
|
17
|
+
"#<#{self.class.name} #{@object.inspect}>"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the JSON representation of the response as a +String+.
|
21
|
+
def to_json(*)
|
22
|
+
schema = @response.schema.resolve(@definitions)
|
23
|
+
if @response.locale
|
24
|
+
I18n.with_locale(@response.locale) do
|
25
|
+
serialize(@object, schema)
|
26
|
+
end
|
27
|
+
else
|
28
|
+
serialize(@object, schema)
|
29
|
+
end.to_json
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def serialize(object, schema, path = nil)
|
35
|
+
return if object.nil? && schema.nullable?
|
36
|
+
raise "#{path || 'response'} can't be nil" if object.nil?
|
37
|
+
|
38
|
+
case schema.type
|
39
|
+
when 'array'
|
40
|
+
item_schema = schema.items.resolve(@definitions)
|
41
|
+
Array(object).map { |item| serialize(item, item_schema, path) }
|
42
|
+
when 'integer'
|
43
|
+
schema.convert(object.to_i)
|
44
|
+
when 'number'
|
45
|
+
schema.convert(object.to_f)
|
46
|
+
when 'object'
|
47
|
+
return if object.blank? # {}
|
48
|
+
|
49
|
+
# Select inherriting schema on polymorphism
|
50
|
+
if (discriminator = schema.discriminator)
|
51
|
+
discriminator_property = schema.properties[discriminator.property_name]
|
52
|
+
schema = discriminator.resolve(
|
53
|
+
object.public_send(
|
54
|
+
discriminator_property.source || discriminator_property.name
|
55
|
+
),
|
56
|
+
@definitions
|
57
|
+
)
|
58
|
+
end
|
59
|
+
# Serialize properties
|
60
|
+
schema.resolve_properties(:read, @definitions).transform_values do |property|
|
61
|
+
serialize(
|
62
|
+
object.public_send(property.source || property.name),
|
63
|
+
property.schema.resolve(@definitions),
|
64
|
+
path.nil? ? property.name : "#{path}.#{property.name}"
|
65
|
+
)
|
66
|
+
end
|
67
|
+
when 'string'
|
68
|
+
schema.convert(
|
69
|
+
case schema.format
|
70
|
+
when 'date'
|
71
|
+
object.to_date
|
72
|
+
when 'date-time'
|
73
|
+
object.to_datetime
|
74
|
+
else
|
75
|
+
object.to_s
|
76
|
+
end
|
77
|
+
)
|
78
|
+
else
|
79
|
+
object
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'controller/parameters_invalid'
|
4
|
+
require_relative 'controller/error_result'
|
5
|
+
require_relative 'controller/parameters'
|
6
|
+
require_relative 'controller/response'
|
7
|
+
require_relative 'controller/methods'
|
8
|
+
require_relative 'controller/base'
|
9
|
+
|
10
|
+
module Jsapi
|
11
|
+
# Provides classes and modules to implement API controllers.
|
12
|
+
module Controller end
|
13
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jsapi
|
4
|
+
module DSL
|
5
|
+
module Callbacks
|
6
|
+
# Defines an OpenAPI callback or refers a reusable callback.
|
7
|
+
#
|
8
|
+
# # define a callback
|
9
|
+
# callback 'foo' do
|
10
|
+
# operation '{$request.query.foo}'
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# # refer a reusable callback
|
14
|
+
# callback ref: 'foo'
|
15
|
+
#
|
16
|
+
# Refers the reusable callback with the same name if neither any
|
17
|
+
# keywords nor a block is specified.
|
18
|
+
#
|
19
|
+
# callback 'foo'
|
20
|
+
#
|
21
|
+
def callback(name = nil, **keywords, &block)
|
22
|
+
_define('callback', name&.inspect) do
|
23
|
+
name = keywords[:ref] if name.nil?
|
24
|
+
keywords = { ref: name } unless keywords.any? || block
|
25
|
+
|
26
|
+
callback_model = _meta_model.add_callback(name, keywords)
|
27
|
+
_eval(callback_model, OpenAPI::Callback, &block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jsapi
|
4
|
+
module DSL
|
5
|
+
module ClassMethods
|
6
|
+
# The API definitions of the current class.
|
7
|
+
def api_definitions(&block)
|
8
|
+
@api_definitions ||= Meta::Definitions.new(self)
|
9
|
+
Definitions.new(@api_definitions, &block) if block
|
10
|
+
@api_definitions
|
11
|
+
end
|
12
|
+
|
13
|
+
# Includes API definitions from +klasses+.
|
14
|
+
def api_include(*klasses)
|
15
|
+
api_definitions { include(*klasses) }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Defines an operation.
|
19
|
+
#
|
20
|
+
# api_operation 'foo', path: '/foo' do
|
21
|
+
# parameter 'bar', type: 'string'
|
22
|
+
# response do
|
23
|
+
# property 'foo', type: 'string'
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# +name+ can be +nil+ if the controller handles one operation only.
|
28
|
+
def api_operation(name = nil, **options, &block)
|
29
|
+
api_definitions { operation(name, **options, &block) }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Defines a reusable parameter.
|
33
|
+
#
|
34
|
+
# api_parameter 'foo', type: 'string'
|
35
|
+
#
|
36
|
+
def api_parameter(name, **options, &block)
|
37
|
+
api_definitions { parameter(name, **options, &block) }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Defines a reusable request body.
|
41
|
+
#
|
42
|
+
# api_request_body 'foo', type: 'string'
|
43
|
+
#
|
44
|
+
def api_request_body(name, **options, &block)
|
45
|
+
api_definitions { request_body(name, **options, &block) }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Specifies the HTTP status code of an error response rendered when an
|
49
|
+
# exception of any of +klasses+ has been raised.
|
50
|
+
#
|
51
|
+
# api_rescue_from Jsapi::Controller::ParametersInvalid, with: 400
|
52
|
+
#
|
53
|
+
def api_rescue_from(*klasses, with: nil)
|
54
|
+
api_definitions { rescue_from(*klasses, with: with) }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Defines a reusable response.
|
58
|
+
#
|
59
|
+
# api_response 'Foo', type: 'object' do
|
60
|
+
# property 'bar', type: 'string'
|
61
|
+
# end
|
62
|
+
def api_response(name, **options, &block)
|
63
|
+
api_definitions { response(name, **options, &block) }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Defines a reusable schema.
|
67
|
+
#
|
68
|
+
# api_schema 'Foo' do
|
69
|
+
# property 'bar', type: 'string'
|
70
|
+
# end
|
71
|
+
def api_schema(name, **options, &block)
|
72
|
+
api_definitions { schema(name, **options, &block) }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Defines the root of an OpenAPI document.
|
76
|
+
#
|
77
|
+
# openapi do
|
78
|
+
# info title: 'Foo', version: '1'
|
79
|
+
# end
|
80
|
+
def openapi(**keywords, &block)
|
81
|
+
api_definitions { openapi(**keywords, &block) }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jsapi
|
4
|
+
module DSL
|
5
|
+
# Used to define top-level API components.
|
6
|
+
class Definitions < Node
|
7
|
+
|
8
|
+
# Includes API definitions from +klasses+.
|
9
|
+
def include(*klasses)
|
10
|
+
klasses.each do |klass|
|
11
|
+
_meta_model.include(klass.api_definitions)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Defines the root of an OpenAPI document.
|
16
|
+
#
|
17
|
+
# openapi do
|
18
|
+
# info title: 'Foo', version: '1'
|
19
|
+
# end
|
20
|
+
def openapi(**keywords, &block)
|
21
|
+
_define('openapi') do
|
22
|
+
_meta_model.openapi_root = keywords
|
23
|
+
OpenAPI::Root.new(_meta_model.openapi_root, &block) if block
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Defines an operation.
|
28
|
+
#
|
29
|
+
# operation 'foo', path: '/foo' do
|
30
|
+
# parameter 'bar', type: 'string'
|
31
|
+
# response do
|
32
|
+
# property 'foo', type: 'string'
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# +name+ can be +nil+ if the controller handles one operation only.
|
37
|
+
def operation(name = nil, **keywords, &block)
|
38
|
+
_define('operation', name&.inspect) do
|
39
|
+
operation_model = _meta_model.add_operation(name, keywords)
|
40
|
+
Operation.new(operation_model, &block) if block
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Defines a reusable parameter.
|
45
|
+
#
|
46
|
+
# parameter 'foo', type: 'string'
|
47
|
+
#
|
48
|
+
def parameter(name, **keywords, &block)
|
49
|
+
_define('parameter', name.inspect) do
|
50
|
+
parameter_model = _meta_model.add_parameter(name, keywords)
|
51
|
+
Parameter.new(parameter_model, &block) if block
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Defines a reusable request body.
|
56
|
+
#
|
57
|
+
# request_body 'foo', type: 'string'
|
58
|
+
#
|
59
|
+
def request_body(name, **keywords, &block)
|
60
|
+
_define('request_body', name.inspect) do
|
61
|
+
request_body_model = _meta_model.add_request_body(name, keywords)
|
62
|
+
RequestBody.new(request_body_model, &block) if block
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Specifies the HTTP status code of an error response rendered when an
|
67
|
+
# exception of any of +klasses+ has been raised.
|
68
|
+
#
|
69
|
+
# rescue_from Jsapi::Controller::ParametersInvalid, with: 400
|
70
|
+
#
|
71
|
+
def rescue_from(*klasses, with: nil)
|
72
|
+
klasses.each do |klass|
|
73
|
+
_meta_model.add_rescue_handler(klass, status: with)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Defines a reusable response.
|
78
|
+
#
|
79
|
+
# response 'Foo', type: 'object' do
|
80
|
+
# property 'bar', type: 'string'
|
81
|
+
# end
|
82
|
+
def response(name, **keywords, &block)
|
83
|
+
_define('response', name.inspect) do
|
84
|
+
response_model = _meta_model.add_response(name, keywords)
|
85
|
+
Response.new(response_model, &block) if block
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Defines a reusable schema.
|
90
|
+
#
|
91
|
+
# schema 'Foo' do
|
92
|
+
# property 'bar', type: 'string'
|
93
|
+
# end
|
94
|
+
def schema(name, **keywords, &block)
|
95
|
+
_define('schema', name.inspect) do
|
96
|
+
schema_model = _meta_model.add_schema(name, keywords)
|
97
|
+
Schema.new(schema_model, &block) if block
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jsapi
|
4
|
+
module DSL
|
5
|
+
# Raised when an error occurred while defining an API component.
|
6
|
+
class Error < StandardError
|
7
|
+
|
8
|
+
# Creates a new error. +origin+ is the innermost position at where
|
9
|
+
# the error occurred.
|
10
|
+
def initialize(error_or_message, origin = nil)
|
11
|
+
@path = Array(origin)
|
12
|
+
super(
|
13
|
+
if error_or_message.respond_to?(:message)
|
14
|
+
error_or_message.message
|
15
|
+
else
|
16
|
+
error_or_message
|
17
|
+
end
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Overrides <code>StandardError#message</code> to append the whole path of
|
22
|
+
# the position at where the error occurred, for example:
|
23
|
+
# <code>{message} (at foo / bar)</code>.
|
24
|
+
def message
|
25
|
+
message = super
|
26
|
+
return message if @path.empty?
|
27
|
+
|
28
|
+
"#{message} (at #{@path.join(' / ')})"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Prepends +origin+ to the path at where the error occurred.
|
32
|
+
def prepend_origin(origin)
|
33
|
+
@path.prepend(origin) if origin.present?
|
34
|
+
self
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jsapi
|
4
|
+
module DSL
|
5
|
+
module Examples
|
6
|
+
# Defines an example.
|
7
|
+
#
|
8
|
+
# example 'foo', value: 'bar'
|
9
|
+
#
|
10
|
+
# example 'foo'
|
11
|
+
#
|
12
|
+
# The default name is <code>'default'</code>.
|
13
|
+
def example(name_or_value = nil, **keywords, &block)
|
14
|
+
_define('example', name_or_value&.inspect) do
|
15
|
+
if keywords.any? || block
|
16
|
+
# example 'foo', value: 'bar', ...
|
17
|
+
name = name_or_value
|
18
|
+
else
|
19
|
+
# example 'foo'
|
20
|
+
name = nil
|
21
|
+
keywords = { value: name_or_value }
|
22
|
+
end
|
23
|
+
|
24
|
+
example = _meta_model.add_example(name, keywords)
|
25
|
+
Node.new(example, &block) if block
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|