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.
Files changed (121) hide show
  1. checksums.yaml +7 -0
  2. data/lib/jsapi/controller/base.rb +21 -0
  3. data/lib/jsapi/controller/error_result.rb +21 -0
  4. data/lib/jsapi/controller/methods.rb +144 -0
  5. data/lib/jsapi/controller/parameters.rb +86 -0
  6. data/lib/jsapi/controller/parameters_invalid.rb +25 -0
  7. data/lib/jsapi/controller/response.rb +84 -0
  8. data/lib/jsapi/controller.rb +13 -0
  9. data/lib/jsapi/dsl/callbacks.rb +32 -0
  10. data/lib/jsapi/dsl/class_methods.rb +85 -0
  11. data/lib/jsapi/dsl/definitions.rb +102 -0
  12. data/lib/jsapi/dsl/error.rb +38 -0
  13. data/lib/jsapi/dsl/examples.rb +30 -0
  14. data/lib/jsapi/dsl/node.rb +62 -0
  15. data/lib/jsapi/dsl/openapi/callback.rb +23 -0
  16. data/lib/jsapi/dsl/openapi/root.rb +12 -0
  17. data/lib/jsapi/dsl/openapi.rb +4 -0
  18. data/lib/jsapi/dsl/operation.rb +118 -0
  19. data/lib/jsapi/dsl/parameter.rb +10 -0
  20. data/lib/jsapi/dsl/request_body.rb +10 -0
  21. data/lib/jsapi/dsl/response.rb +33 -0
  22. data/lib/jsapi/dsl/schema.rb +87 -0
  23. data/lib/jsapi/dsl.rb +24 -0
  24. data/lib/jsapi/json/array.rb +35 -0
  25. data/lib/jsapi/json/boolean.rb +17 -0
  26. data/lib/jsapi/json/integer.rb +15 -0
  27. data/lib/jsapi/json/null.rb +27 -0
  28. data/lib/jsapi/json/number.rb +15 -0
  29. data/lib/jsapi/json/object.rb +53 -0
  30. data/lib/jsapi/json/string.rb +29 -0
  31. data/lib/jsapi/json/value.rb +47 -0
  32. data/lib/jsapi/json.rb +41 -0
  33. data/lib/jsapi/meta/attributes/class_methods.rb +112 -0
  34. data/lib/jsapi/meta/attributes/type_caster.rb +48 -0
  35. data/lib/jsapi/meta/attributes.rb +4 -0
  36. data/lib/jsapi/meta/base.rb +41 -0
  37. data/lib/jsapi/meta/base_reference.rb +33 -0
  38. data/lib/jsapi/meta/definitions.rb +226 -0
  39. data/lib/jsapi/meta/example/model.rb +44 -0
  40. data/lib/jsapi/meta/example/reference.rb +15 -0
  41. data/lib/jsapi/meta/example.rb +19 -0
  42. data/lib/jsapi/meta/existence.rb +69 -0
  43. data/lib/jsapi/meta/invalid_argument_error.rb +11 -0
  44. data/lib/jsapi/meta/openapi/callback/model.rb +36 -0
  45. data/lib/jsapi/meta/openapi/callback/reference.rb +16 -0
  46. data/lib/jsapi/meta/openapi/callback.rb +21 -0
  47. data/lib/jsapi/meta/openapi/contact.rb +34 -0
  48. data/lib/jsapi/meta/openapi/external_documentation.rb +28 -0
  49. data/lib/jsapi/meta/openapi/info.rb +52 -0
  50. data/lib/jsapi/meta/openapi/license.rb +28 -0
  51. data/lib/jsapi/meta/openapi/link/model.rb +48 -0
  52. data/lib/jsapi/meta/openapi/link/reference.rb +16 -0
  53. data/lib/jsapi/meta/openapi/link.rb +21 -0
  54. data/lib/jsapi/meta/openapi/oauth_flow.rb +50 -0
  55. data/lib/jsapi/meta/openapi/root.rb +134 -0
  56. data/lib/jsapi/meta/openapi/security_requirement.rb +27 -0
  57. data/lib/jsapi/meta/openapi/security_scheme/api_key.rb +38 -0
  58. data/lib/jsapi/meta/openapi/security_scheme/base.rb +16 -0
  59. data/lib/jsapi/meta/openapi/security_scheme/http/basic.rb +31 -0
  60. data/lib/jsapi/meta/openapi/security_scheme/http/bearer.rb +37 -0
  61. data/lib/jsapi/meta/openapi/security_scheme/http/other.rb +37 -0
  62. data/lib/jsapi/meta/openapi/security_scheme/http.rb +31 -0
  63. data/lib/jsapi/meta/openapi/security_scheme/oauth2.rb +47 -0
  64. data/lib/jsapi/meta/openapi/security_scheme/open_id_connect.rb +33 -0
  65. data/lib/jsapi/meta/openapi/security_scheme.rb +51 -0
  66. data/lib/jsapi/meta/openapi/server.rb +34 -0
  67. data/lib/jsapi/meta/openapi/server_variable.rb +34 -0
  68. data/lib/jsapi/meta/openapi/tag.rb +34 -0
  69. data/lib/jsapi/meta/openapi/version.rb +41 -0
  70. data/lib/jsapi/meta/openapi.rb +16 -0
  71. data/lib/jsapi/meta/operation.rb +186 -0
  72. data/lib/jsapi/meta/parameter/model.rb +170 -0
  73. data/lib/jsapi/meta/parameter/reference.rb +30 -0
  74. data/lib/jsapi/meta/parameter.rb +19 -0
  75. data/lib/jsapi/meta/property.rb +62 -0
  76. data/lib/jsapi/meta/reference_error.rb +12 -0
  77. data/lib/jsapi/meta/request_body/model.rb +65 -0
  78. data/lib/jsapi/meta/request_body/reference.rb +14 -0
  79. data/lib/jsapi/meta/request_body.rb +19 -0
  80. data/lib/jsapi/meta/rescue_handler.rb +26 -0
  81. data/lib/jsapi/meta/response/model.rb +72 -0
  82. data/lib/jsapi/meta/response/reference.rb +17 -0
  83. data/lib/jsapi/meta/response.rb +19 -0
  84. data/lib/jsapi/meta/schema/array.rb +42 -0
  85. data/lib/jsapi/meta/schema/base.rb +146 -0
  86. data/lib/jsapi/meta/schema/boolean.rb +9 -0
  87. data/lib/jsapi/meta/schema/boundary.rb +37 -0
  88. data/lib/jsapi/meta/schema/conversion.rb +28 -0
  89. data/lib/jsapi/meta/schema/delegator.rb +26 -0
  90. data/lib/jsapi/meta/schema/discriminator.rb +36 -0
  91. data/lib/jsapi/meta/schema/integer.rb +9 -0
  92. data/lib/jsapi/meta/schema/number.rb +9 -0
  93. data/lib/jsapi/meta/schema/numeric.rb +56 -0
  94. data/lib/jsapi/meta/schema/object.rb +85 -0
  95. data/lib/jsapi/meta/schema/reference.rb +38 -0
  96. data/lib/jsapi/meta/schema/string.rb +58 -0
  97. data/lib/jsapi/meta/schema/validation/base.rb +29 -0
  98. data/lib/jsapi/meta/schema/validation/enum.rb +26 -0
  99. data/lib/jsapi/meta/schema/validation/max_items.rb +26 -0
  100. data/lib/jsapi/meta/schema/validation/max_length.rb +26 -0
  101. data/lib/jsapi/meta/schema/validation/maximum.rb +51 -0
  102. data/lib/jsapi/meta/schema/validation/min_items.rb +26 -0
  103. data/lib/jsapi/meta/schema/validation/min_length.rb +26 -0
  104. data/lib/jsapi/meta/schema/validation/minimum.rb +51 -0
  105. data/lib/jsapi/meta/schema/validation/multiple_of.rb +24 -0
  106. data/lib/jsapi/meta/schema/validation/pattern.rb +30 -0
  107. data/lib/jsapi/meta/schema/validation.rb +12 -0
  108. data/lib/jsapi/meta/schema.rb +61 -0
  109. data/lib/jsapi/meta.rb +23 -0
  110. data/lib/jsapi/model/attributes.rb +22 -0
  111. data/lib/jsapi/model/base.rb +34 -0
  112. data/lib/jsapi/model/error.rb +15 -0
  113. data/lib/jsapi/model/errors.rb +51 -0
  114. data/lib/jsapi/model/naming.rb +28 -0
  115. data/lib/jsapi/model/nestable.rb +37 -0
  116. data/lib/jsapi/model/nested_error.rb +54 -0
  117. data/lib/jsapi/model/validations.rb +27 -0
  118. data/lib/jsapi/model.rb +15 -0
  119. data/lib/jsapi/version.rb +8 -0
  120. data/lib/jsapi.rb +8 -0
  121. 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