jsapi 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb2e35d1d98201647ee5ec6087437891fc7781e94c717804275a6ed1ee870267
4
- data.tar.gz: 1295f4128a5e096e64be1243f532ada34a58032860b87515836697ee48c7a51f
3
+ metadata.gz: ba5cf0667b03c271ef93b725cbcdcc25d51b2afaf33efe43372c66880ae46996
4
+ data.tar.gz: 1826d5fbc22a972c8f38476a45eff4052930480d7b1a195255422de272d181a4
5
5
  SHA512:
6
- metadata.gz: 0f826bdf8be20affd8181feaf0f507705cf87ab9960711e1715dd4b2a7abb6ba072654532e99a7701359b8a38685f80c19aa39be1e0538f27befd460a8c8c74c
7
- data.tar.gz: fbd3593855e17a5e6621e572ed292073d5849f802dc39febbb20d59e0f2a3133118cbc1c84413f8cfbd4a17047d28b5989a476abe8904f6a0c2f8390abda4dbf
6
+ metadata.gz: 63ca2ff516d31035884694dfb5a1969b1a697800f9afe694d30e6d250ca181bca0ae71d096d3e566d7de4a50f326233e9e5c9c3f199f38171d65bd27e36e6dec
7
+ data.tar.gz: f1b25727248b4bad12b2d57d665f86ee2f75fe826de22cd1d86b4e435152e8c0d2cb07152666b23a61bfb14d3810d516f3eeee53369f6f837559efc29156e996
@@ -35,7 +35,7 @@ module Jsapi
35
35
  #
36
36
  # Raises an InvalidArgumentError when the value of +:omit+ is invalid.
37
37
  def api_operation(operation_name = nil, omit: nil, status: nil, strong: false, &block)
38
- _perform_api_operation(
38
+ _api_operation(
39
39
  operation_name,
40
40
  bang: false,
41
41
  omit: omit,
@@ -53,7 +53,7 @@ module Jsapi
53
53
  # end
54
54
  #
55
55
  def api_operation!(operation_name = nil, omit: nil, status: nil, strong: false, &block)
56
- _perform_api_operation(
56
+ _api_operation(
57
57
  operation_name,
58
58
  bang: true,
59
59
  omit: omit,
@@ -78,7 +78,7 @@ module Jsapi
78
78
  def api_params(operation_name = nil, strong: false)
79
79
  definitions = api_definitions
80
80
  _api_params(
81
- _api_operation(operation_name, definitions),
81
+ _find_api_operation(operation_name, definitions),
82
82
  definitions,
83
83
  strong: strong
84
84
  )
@@ -100,7 +100,7 @@ module Jsapi
100
100
  # Raises an InvalidArgumentError when the value of +:omit+ is invalid.
101
101
  def api_response(result, operation_name = nil, omit: nil, status: nil)
102
102
  definitions = api_definitions
103
- operation = _api_operation(operation_name, definitions)
103
+ operation = _find_api_operation(operation_name, definitions)
104
104
  response = _api_response(operation, status, definitions)
105
105
 
106
106
  Response.new(result, response, api_definitions, omit: omit)
@@ -108,35 +108,9 @@ module Jsapi
108
108
 
109
109
  private
110
110
 
111
- def _api_operation(operation_name, definitions)
112
- operation = definitions.operation(operation_name)
113
- return operation if operation
114
-
115
- raise "operation not defined: #{operation_name}"
116
- end
117
-
118
- def _api_params(operation, definitions, strong:)
119
- (operation.model || Model::Base).new(
120
- Parameters.new(
121
- params.except(:action, :controller, :format).permit!,
122
- headers,
123
- operation,
124
- definitions,
125
- strong: strong
126
- )
127
- )
128
- end
129
-
130
- def _api_response(operation, status, definitions)
131
- response = operation.response(status)
132
- return response.resolve(definitions) if response
133
-
134
- raise "status code not defined: #{status}"
135
- end
136
-
137
- def _perform_api_operation(operation_name, bang:, omit:, status:, strong:, &block)
111
+ def _api_operation(operation_name, bang:, omit:, status:, strong:, &block)
138
112
  definitions = api_definitions
139
- operation = _api_operation(operation_name, definitions)
113
+ operation = _find_api_operation(operation_name, definitions)
140
114
  response = _api_response(operation, status, definitions)
141
115
 
142
116
  if block
@@ -172,6 +146,32 @@ module Jsapi
172
146
  end
173
147
  self.content_type = response.content_type
174
148
  end
149
+
150
+ def _api_params(operation, definitions, strong:)
151
+ (operation.model || Model::Base).new(
152
+ Parameters.new(
153
+ params.except(:action, :controller, :format).permit!,
154
+ headers,
155
+ operation,
156
+ definitions,
157
+ strong: strong
158
+ )
159
+ )
160
+ end
161
+
162
+ def _api_response(operation, status, definitions)
163
+ response = operation.response(status)
164
+ return response.resolve(definitions) if response
165
+
166
+ raise "status code not defined: #{status}"
167
+ end
168
+
169
+ def _find_api_operation(operation_name, definitions)
170
+ operation = definitions.find_operation(operation_name)
171
+ return operation if operation
172
+
173
+ raise "operation not defined: #{operation_name}"
174
+ end
175
175
  end
176
176
  end
177
177
  end
@@ -13,7 +13,10 @@ module Jsapi
13
13
 
14
14
  # The API definitions of the current class.
15
15
  def api_definitions(&block)
16
- @api_definitions ||= Meta::Definitions.new(self)
16
+ @api_definitions ||= Meta::Definitions.new(
17
+ owner: self,
18
+ parent: superclass.try(:api_definitions)
19
+ )
17
20
  Definitions.new(@api_definitions, &block) if block
18
21
  @api_definitions
19
22
  end
@@ -19,7 +19,7 @@ module Jsapi
19
19
  # Includes API definitions from +klasses+.
20
20
  def include(*klasses)
21
21
  klasses.each do |klass|
22
- _meta_model.include(klass.api_definitions)
22
+ _meta_model.add_included(klass.api_definitions)
23
23
  end
24
24
  end
25
25
 
@@ -37,8 +37,8 @@ module Jsapi
37
37
  # end
38
38
  def openapi(**keywords, &block)
39
39
  _define('openapi') do
40
- _meta_model.openapi_root = keywords
41
- OpenAPI::Root.new(_meta_model.openapi_root, &block) if block
40
+ _meta_model.openapi = keywords
41
+ OpenAPI::Root.new(_meta_model.openapi, &block) if block
42
42
  end
43
43
  end
44
44
 
@@ -88,7 +88,7 @@ module Jsapi
88
88
  #
89
89
  def rescue_from(*klasses, with: nil)
90
90
  klasses.each do |klass|
91
- _meta_model.add_rescue_handler(klass, status: with)
91
+ _meta_model.add_rescue_handler({ error_class: klass, status: with })
92
92
  end
93
93
  end
94
94
 
@@ -6,11 +6,11 @@ module Jsapi
6
6
  module Attributes
7
7
  # Defines an attribute.
8
8
  def attribute(name, type = Object,
9
- keys: nil,
10
- values: nil,
11
9
  default: nil,
12
10
  default_key: nil,
13
- writer: true)
11
+ keys: nil,
12
+ read_only: false,
13
+ values: nil)
14
14
 
15
15
  (@attribute_names ||= []) << name.to_sym
16
16
 
@@ -24,20 +24,19 @@ module Jsapi
24
24
 
25
25
  case type
26
26
  when Array
27
- if writer
27
+ unless read_only
28
28
  singular_name = name.to_s.singularize
29
+ add_method = "add_#{singular_name}"
30
+
29
31
  type_caster = TypeCaster.new(type.first, values: values, name: singular_name)
30
32
 
31
33
  # Attribute writer
32
34
  define_method("#{name}=") do |argument|
33
- instance_variable_set(
34
- instance_variable_name,
35
- Array.wrap(argument).map { |element| type_caster.cast(element) }
36
- )
35
+ Array.wrap(argument).each { |element| send(add_method, element) }
37
36
  end
38
37
 
39
- # add_{singular_name} method
40
- define_method("add_#{singular_name}") do |argument = nil|
38
+ # Add method
39
+ define_method(add_method) do |argument = nil|
41
40
  type_caster.cast(argument).tap do |casted_argument|
42
41
  if instance_variable_defined?(instance_variable_name)
43
42
  instance_variable_get(instance_variable_name)
@@ -49,20 +48,27 @@ module Jsapi
49
48
  end
50
49
  when Hash
51
50
  singular_name = name.to_s.singularize
51
+ add_method = "add_#{singular_name}"
52
+
52
53
  key_type, value_type = type.first
53
54
  key_type_caster = TypeCaster.new(key_type, values: keys, name: 'key')
54
55
 
55
- # hash value reader
56
+ # Hash value reader
56
57
  define_method(singular_name) do |key = nil|
57
58
  key = default_key if key.to_s.empty?
58
59
  send(name)&.[](key_type_caster.cast(key))
59
60
  end
60
61
 
61
- if writer
62
+ unless read_only
62
63
  value_type_caster = TypeCaster.new(value_type, values: values)
63
64
 
64
- # add_{singular_name} method
65
- define_method("add_#{singular_name}") do |key_or_value, value = nil|
65
+ # Attribute writer
66
+ define_method("#{name}=") do |argument|
67
+ argument.each { |key, value| send(add_method, key, value) }
68
+ end
69
+
70
+ # Add method
71
+ define_method(add_method) do |key_or_value, value = nil|
66
72
  if value.nil? && default_key
67
73
  key = default_key
68
74
  value = key_or_value
@@ -89,7 +95,7 @@ module Jsapi
89
95
  value.nil? ? default || false : value
90
96
  end if values == [true, false]
91
97
 
92
- if writer
98
+ unless read_only
93
99
  type_caster = TypeCaster.new(type, values: values, name: name)
94
100
 
95
101
  # Attribute writer
@@ -20,12 +20,13 @@ module Jsapi
20
20
  end
21
21
  end
22
22
 
23
- def inspect # :nodoc:
23
+ def inspect(*attributes) # :nodoc:
24
24
  klass = self.class
25
+ attribute_names = klass.attribute_names
26
+ attribute_names = attributes & attribute_names if attributes.any?
27
+
25
28
  "#<#{klass.name} #{
26
- klass.attribute_names.map do |name|
27
- "#{name}: #{send(name).inspect}"
28
- end.join(', ')
29
+ attribute_names.map { |name| "#{name}: #{send(name).inspect}" }.join(', ')
29
30
  }>"
30
31
  end
31
32
 
@@ -10,10 +10,9 @@ module Jsapi
10
10
  # The name of the referred object.
11
11
  attribute :ref, String
12
12
 
13
- # Returns the name of the method to be called to look up the referred object
14
- # in a Definitions instance.
15
- def self.lookup_method_name
16
- @lookup_method_name ||= name.delete_suffix('::Reference').demodulize.underscore
13
+ # Derrives the component type from the inner most module name.
14
+ def self.component_type
15
+ @component_type ||= name.split('::')[-2].underscore
17
16
  end
18
17
 
19
18
  # Returns true.
@@ -25,7 +24,7 @@ module Jsapi
25
24
  #
26
25
  # Raises a ReferenceError if +ref+ could not be resolved.
27
26
  def resolve(definitions)
28
- object = definitions.send(self.class.lookup_method_name, ref)
27
+ object = definitions.find_component(self.class.component_type, ref)
29
28
  raise ReferenceError, ref if object.nil?
30
29
 
31
30
  object.resolve(definitions)
@@ -14,7 +14,8 @@ module Jsapi
14
14
  }
15
15
 
16
16
  # Creates a new type caster for +klass+.
17
- def initialize(klass, name: 'value', values: nil)
17
+ def initialize(klass = nil, name: 'value', values: nil)
18
+ klass = Object if klass.nil?
18
19
  @caster =
19
20
  case klass.name
20
21
  when 'String'
@@ -7,12 +7,12 @@ module Jsapi
7
7
  ##
8
8
  # :attr: read
9
9
  # The default value of parameters and properties when reading requests.
10
- attribute :within_requests, Object
10
+ attribute :within_requests
11
11
 
12
12
  ##
13
13
  # :attr: write
14
14
  # The default value of properties when writing responses.
15
- attribute :within_responses, Object
15
+ attribute :within_responses
16
16
 
17
17
  # Returns the default value within +context+.
18
18
  def value(context:)
@@ -2,170 +2,225 @@
2
2
 
3
3
  module Jsapi
4
4
  module Meta
5
- class Definitions
6
- extend Base::Attributes
7
-
5
+ class Definitions < Base::Model
8
6
  ##
9
7
  # :attr: defaults
10
- # The general default values.
8
+ # The Defaults.
11
9
  attribute :defaults, { String => Defaults }, keys: Schema::TYPES, default: {}
12
10
 
13
11
  ##
14
- # :attr: openapi_root
15
- # The OpenAPI::Root.
16
- attribute :openapi_root, OpenAPI::Root
12
+ # :attr: included
13
+ # The Definitions included.
14
+ attribute :included, [Definitions], default: []
17
15
 
18
- attr_reader :callbacks, :operations, :parameters, :request_bodies,
19
- :rescue_handlers, :responses, :schemas
16
+ ##
17
+ # :attr: on_rescues
18
+ # The methods or procs to be called when rescuing an exception.
19
+ attribute :on_rescues, [], default: []
20
20
 
21
- def initialize(owner = nil)
22
- @owner = owner
23
- @callbacks = { on_rescue: [] }
24
- @operations = {}
25
- @parameters = {}
26
- @request_bodies = {}
27
- @rescue_handlers = []
28
- @responses = {}
29
- @schemas = {}
30
- @self_and_included = [self]
31
- end
21
+ ##
22
+ # :attr: openapi
23
+ # The OpenAPI root object.
24
+ attribute :openapi, OpenAPI
32
25
 
33
- def add_on_rescue(method_or_proc)
34
- @callbacks[:on_rescue] << method_or_proc
35
- end
26
+ ##
27
+ # :attr: operations
28
+ # The Operation objects.
29
+ attribute :operations, { String => Operation }, default: {}
36
30
 
37
- def add_operation(name = nil, keywords = {})
38
- name = name.nil? ? default_operation_name : name.to_s
39
- @operations[name] = Operation.new(name, keywords.reverse_merge(path: default_path))
40
- end
31
+ ##
32
+ # :attr_reader: owner
33
+ # The class to which it is assigned.
34
+ attribute :owner, read_only: true
41
35
 
42
- def add_parameter(name, keywords = {})
43
- name = name.to_s
44
- @parameters[name] = Parameter.new(name, keywords)
36
+ ##
37
+ # :attr: parameters
38
+ # The reusable Parameter objects.
39
+ attribute :parameters, { String => Parameter }, default: {}
40
+
41
+ ##
42
+ # :attr_reader: parent
43
+ # The Definitions from which it inherits.
44
+ attribute :parent, read_only: true
45
+
46
+ ##
47
+ # :attr: rescue_handlers
48
+ # The RescueHandler objects.
49
+ attribute :rescue_handlers, [RescueHandler], default: []
50
+
51
+ ##
52
+ # :attr: request_bodies
53
+ # The reusable RequestBody objects.
54
+ attribute :request_bodies, { String => RequestBody }, default: {}
55
+
56
+ ##
57
+ # :attr: responses
58
+ # The reusable Response objects.
59
+ attribute :responses, { String => Response }, default: {}
60
+
61
+ ##
62
+ # :attr: schemas
63
+ # The reusable Schema objects.
64
+ attribute :schemas, { String => Schema }, default: {}
65
+
66
+ undef add_included, add_operation, add_parameter
67
+
68
+ def initialize(keywords = {})
69
+ @owner = keywords.delete(:owner)
70
+ @parent = keywords.delete(:parent)
71
+
72
+ super(keywords)
45
73
  end
46
74
 
47
- def add_request_body(name, keywords = {})
48
- @request_bodies[name.to_s] = RequestBody.new(keywords)
75
+ def add_included(definitions) # :nodoc:
76
+ if circular_dependency?(definitions)
77
+ raise ArgumentError, 'detected circular dependency between ' \
78
+ "#{owner.inspect} and " \
79
+ "#{definitions.owner.inspect}"
80
+ end
81
+
82
+ (@included ||= []) << definitions
83
+ self
49
84
  end
50
85
 
51
- def add_rescue_handler(klass, status: nil)
52
- @rescue_handlers << RescueHandler.new(klass, status: status)
86
+ def add_operation(name = nil, keywords = {}) # :nodoc:
87
+ name = name.nil? ? default_operation_name : name.to_s
88
+ keywords = keywords.reverse_merge(path: default_path)
89
+ (@operations ||= {})[name] = Operation.new(name, keywords)
53
90
  end
54
91
 
55
- def add_response(name, keywords = {})
92
+ def add_parameter(name, keywords = {}) # :nodoc:
56
93
  name = name.to_s
57
- @responses[name] = Response.new(keywords)
94
+ (@parameters ||= {})[name] = Parameter.new(name, keywords)
58
95
  end
59
96
 
60
- def add_schema(name, keywords = {})
61
- name = name.to_s
62
- @schemas[name] = Schema.new(keywords)
97
+ # Returns an array containing itself and all of the definitions inherited/included.
98
+ def ancestors
99
+ [self].tap do |ancestors|
100
+ (included + Array(parent)).each do |included_or_parent|
101
+ included_or_parent.ancestors.each do |definitions|
102
+ ancestors << definitions
103
+ end
104
+ end
105
+ end.uniq
63
106
  end
64
107
 
108
+ # Returns the default value for +type+ within +context+.
65
109
  def default_value(type, context: nil)
66
110
  return unless (type = type.to_s).present?
67
111
 
68
- @self_and_included.each do |definitions|
112
+ ancestors.each do |definitions|
69
113
  default = definitions.default(type)
70
114
  return default.value(context: context) if default
71
115
  end
72
116
  nil
73
117
  end
74
118
 
75
- def include(definitions)
76
- return if @self_and_included.include?(definitions)
119
+ # Returns the component with the specified type and name.
120
+ def find_component(type, name)
121
+ return unless (name = name.to_s).present?
122
+
123
+ ancestors.each do |definitions|
124
+ component = definitions.send(type, name)
125
+ return component if component.present?
126
+ end
127
+ nil
128
+ end
129
+
130
+ # Returns the operation with the specified name.
131
+ def find_operation(name = nil)
132
+ return find_component(:operation, name) if name.present?
77
133
 
78
- @self_and_included << definitions
134
+ # Return the one and only operation
135
+ operations.values.first if operations.one?
79
136
  end
80
137
 
81
- def inspect # :nodoc:
82
- "#<#{self.class.name} #{
83
- %i[owner operations parameters request_bodies responses schemas
84
- openapi_root rescue_handlers].map do |name|
85
- "#{name}: #{instance_variable_get("@#{name}").inspect}"
86
- end.join(', ')
87
- }>"
138
+ def inspect(*attributes) # :nodoc:
139
+ super(*(attributes.presence || %i[owner parent included]))
88
140
  end
89
141
 
90
142
  # Returns a hash representing the \JSON \Schema document for +name+.
91
143
  def json_schema_document(name)
92
- schema(name)&.to_json_schema&.tap do |hash|
93
- definitions =
94
- @self_and_included
95
- .map(&:schemas)
96
- .reduce(&:merge)
97
- .except(name.to_s)
98
- .transform_values(&:to_json_schema)
144
+ find_component(:schema, name)&.to_json_schema&.tap do |hash|
145
+ definitions = ancestors
146
+ .map(&:schemas)
147
+ .reduce(&:merge)
148
+ .except(name.to_s)
149
+ .transform_values(&:to_json_schema)
99
150
 
100
151
  hash[:definitions] = definitions if definitions.any?
101
152
  end
102
153
  end
103
154
 
155
+ # Returns the methods or procs to be called when rescuing an exception.
104
156
  def on_rescue_callbacks
105
- @self_and_included.flat_map do |definitions|
106
- definitions.callbacks[:on_rescue]
107
- end
157
+ ancestors.flat_map(&:on_rescues)
108
158
  end
109
159
 
110
160
  # Returns a hash representing the \OpenAPI document for +version+.
111
-
161
+ #
112
162
  # Raises an +ArgumentError+ if +version+ is not supported.
113
163
  def openapi_document(version = nil)
114
164
  version = OpenAPI::Version.from(version)
115
165
 
116
- (openapi_root&.to_openapi(version, self) || {}).tap do |h|
117
- h[:paths] = openapi_paths(version)
166
+ operations = ancestors.map(&:operations).reduce(&:reverse_merge).values
167
+
168
+ components = if version.major == 2
169
+ {
170
+ definitions: :schemas,
171
+ parameters: :parameters,
172
+ responses: :responses
173
+ }
174
+ else
175
+ {
176
+ schemas: :schemas,
177
+ parameters: :parameters,
178
+ requestBodies: :request_bodies,
179
+ responses: :responses
180
+ }
181
+ end
182
+
183
+ openapi_components = ancestors.map do |definitions|
184
+ components.transform_values do |method|
185
+ definitions.send(method).transform_values do |component|
186
+ case method
187
+ when :parameters
188
+ component.to_openapi(version, self).first
189
+ when :responses
190
+ component.to_openapi(version, self)
191
+ else
192
+ component.to_openapi(version)
193
+ end
194
+ end.presence
195
+ end.compact
196
+ end.reduce(&:reverse_merge)
197
+
198
+ (openapi&.to_openapi(version, self) || {}).tap do |h|
199
+ h[:paths] = operations
200
+ .group_by { |operation| operation.path || default_path }
201
+ .transform_values do |op|
202
+ op.index_by(&:method).transform_values do |operation|
203
+ operation.to_openapi(version, self)
204
+ end
205
+ end.presence
118
206
 
119
207
  if version.major == 2
120
- h.merge!(
121
- definitions: openapi_schemas(version),
122
- parameters: openapi_parameters(version),
123
- responses: openapi_responses(version)
124
- )
125
- operations = @self_and_included.map(&:operations).reduce(&:reverse_merge).values
126
-
127
208
  consumes = operations.filter_map { |operation| operation.consumes(self) }
128
209
  h[:consumes] = consumes.uniq.sort if consumes.present?
129
210
 
130
211
  produces = operations.flat_map { |operation| operation.produces(self) }
131
212
  h[:produces] = produces.uniq.sort if produces.present?
132
- else
133
- h[:components] = (h[:components] || {}).merge(
134
- schemas: openapi_schemas(version),
135
- parameters: openapi_parameters(version),
136
- requestBodies: openapi_request_bodies(version),
137
- responses: openapi_responses(version)
138
- ).compact.presence
213
+
214
+ h.merge!(openapi_components)
215
+ elsif openapi_components.any?
216
+ (h[:components] ||= {}).merge!(openapi_components)
139
217
  end
140
218
  end.compact
141
219
  end
142
220
 
143
- def operation(name = nil)
144
- if (name = name.to_s).present?
145
- definitions = @self_and_included.find { |d| d.operations.key?(name) }
146
- definitions.operations[name] if definitions
147
- elsif @operations.one?
148
- # return the one and only operation
149
- @operations.values.first
150
- end
151
- end
152
-
153
- def parameter(name)
154
- return unless (name = name.to_s).present?
155
-
156
- definitions = @self_and_included.find { |d| d.parameters.key?(name) }
157
- definitions.parameters[name] if definitions
158
- end
159
-
160
- def request_body(name)
161
- return unless (name = name.to_s).present?
162
-
163
- definitions = @self_and_included.find { |d| d.request_bodies.key?(name) }
164
- definitions.request_bodies[name] if definitions
165
- end
166
-
221
+ # Returns the first RescueHandler to handle +exception+, or nil if no one could be found.
167
222
  def rescue_handler_for(exception)
168
- @self_and_included.each do |definitions|
223
+ ancestors.each do |definitions|
169
224
  definitions.rescue_handlers.each do |rescue_handler|
170
225
  return rescue_handler if rescue_handler.match?(exception)
171
226
  end
@@ -173,22 +228,15 @@ module Jsapi
173
228
  nil
174
229
  end
175
230
 
176
- def response(name)
177
- return unless (name = name.to_s).present?
178
-
179
- definitions = @self_and_included.find { |d| d.responses.key?(name) }
180
- definitions.responses[name] if definitions
181
- end
231
+ private
182
232
 
183
- def schema(name)
184
- return unless (name = name.to_s).present?
233
+ def circular_dependency?(other)
234
+ return true if other == self
235
+ return false if other.included.none?
185
236
 
186
- definitions = @self_and_included.find { |d| d.schemas.key?(name) }
187
- definitions.schemas[name] if definitions
237
+ other.included.any? { |included| circular_dependency?(included) }
188
238
  end
189
239
 
190
- private
191
-
192
240
  def default_operation_name
193
241
  @default_operation_name ||=
194
242
  @owner.to_s.demodulize.delete_suffix('Controller').underscore
@@ -197,46 +245,6 @@ module Jsapi
197
245
  def default_path
198
246
  @default_path ||= "/#{default_operation_name}"
199
247
  end
200
-
201
- def openapi_parameters(version)
202
- @self_and_included
203
- .map(&:parameters).reduce(&:merge)
204
- .transform_values do |parameter|
205
- parameter.to_openapi(version, self).first
206
- end.presence
207
- end
208
-
209
- def openapi_paths(version)
210
- @self_and_included
211
- .map(&:operations).reduce(&:merge).values
212
- .group_by { |operation| operation.path || default_path }
213
- .transform_values do |operations|
214
- operations.index_by(&:method).transform_values do |operation|
215
- operation.to_openapi(version, self)
216
- end
217
- end.presence
218
- end
219
-
220
- def openapi_request_bodies(version)
221
- @self_and_included
222
- .map(&:request_bodies).reduce(&:merge).transform_values do |request_body|
223
- request_body.to_openapi(version)
224
- end.presence
225
- end
226
-
227
- def openapi_responses(version)
228
- @self_and_included
229
- .map(&:responses).reduce(&:merge).transform_values do |response|
230
- response.to_openapi(version, self)
231
- end.presence
232
- end
233
-
234
- def openapi_schemas(version)
235
- @self_and_included
236
- .map(&:schemas).reduce(&:merge).transform_values do |schema|
237
- schema.to_openapi(version)
238
- end.presence
239
- end
240
248
  end
241
249
  end
242
250
  end
@@ -8,7 +8,9 @@ module Jsapi
8
8
  class Model < Meta::Base::Model
9
9
  ##
10
10
  # :attr: operations
11
- attribute :operations, writer: false, default: {}
11
+ attribute :operations, { String => Object }, default: {}
12
+
13
+ undef add_operation
12
14
 
13
15
  # Adds a callback operation.
14
16
  #
@@ -19,10 +21,6 @@ module Jsapi
19
21
  (@operations ||= {})[expression.to_s] = Operation.new(nil, keywords)
20
22
  end
21
23
 
22
- def operation(expression) # :nodoc:
23
- @operations&.[](expression&.to_s)
24
- end
25
-
26
24
  # Returns a hash representing the \OpenAPI callback object.
27
25
  def to_openapi(version, definitions)
28
26
  operations.transform_values do |operation|
@@ -4,6 +4,10 @@ module Jsapi
4
4
  module Meta
5
5
  module OpenAPI
6
6
  module Extensions
7
+ ##
8
+ # :attr: openapi_extensions
9
+ # The \OpenAPI extensions.
10
+
7
11
  # Adds an \OpenAPI extension.
8
12
  #
9
13
  # Raises an +ArgumentError+ if +name+ is blank.
@@ -13,11 +17,18 @@ module Jsapi
13
17
  openapi_extensions["x-#{name}".to_sym] = value
14
18
  end
15
19
 
16
- # Returns a hash containing the \OpenAPI extensions.
17
- def openapi_extensions
20
+ def openapi_extensions # :nodoc:
18
21
  @openapi_extensions ||= {}
19
22
  end
20
23
 
24
+ def openapi_extensions=(extensions) # :nodoc:
25
+ @openapi_extensions = {}
26
+
27
+ extensions.each do |name, value|
28
+ add_openapi_extension(name, value)
29
+ end
30
+ end
31
+
21
32
  private
22
33
 
23
34
  def with_openapi_extensions(keywords = {}) # :nodoc:
@@ -39,14 +39,17 @@ module Jsapi
39
39
  ##
40
40
  # :attr_reader: schema
41
41
  # The Schema of the header.
42
- attribute :schema, writer: false
42
+ attribute :schema, read_only: true
43
43
 
44
44
  def initialize(keywords = {})
45
45
  raise ArgumentError, "type can't be object" if keywords[:type] == 'object'
46
46
 
47
47
  keywords = keywords.dup
48
- super(keywords.extract!(:collection_format, :deprecated, :description, :examples))
49
-
48
+ super(
49
+ keywords.extract!(
50
+ :collection_format, :deprecated, :description, :examples, :openapi_extensions
51
+ )
52
+ )
50
53
  add_example(value: keywords.delete(:example)) if keywords.key?(:example)
51
54
 
52
55
  @schema = Schema.new(keywords)
@@ -17,3 +17,16 @@ require_relative 'openapi/server'
17
17
  require_relative 'openapi/link'
18
18
  require_relative 'openapi/tag'
19
19
  require_relative 'openapi/root'
20
+
21
+ module Jsapi
22
+ module Meta
23
+ module OpenAPI
24
+ class << self
25
+ # Creates a new \OpenAPI root object.
26
+ def new(keywords = {})
27
+ Root.new(keywords)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -48,12 +48,12 @@ module Jsapi
48
48
  ##
49
49
  # :attr_reader: name
50
50
  # The name of the operation.
51
- attribute :name, writer: false
51
+ attribute :name, read_only: true
52
52
 
53
53
  ##
54
54
  # :attr: parameters
55
55
  # The parameters of the operation.
56
- attribute :parameters, { String => Parameter }, default: {}, writer: false
56
+ attribute :parameters, { String => Parameter }, default: {}
57
57
 
58
58
  ##
59
59
  # :attr: path
@@ -104,6 +104,8 @@ module Jsapi
104
104
  # The tags used to group operations in an \OpenAPI document.
105
105
  attribute :tags, [String]
106
106
 
107
+ undef :add_parameter
108
+
107
109
  def initialize(name = nil, keywords = {})
108
110
  @name = name&.to_s
109
111
  super(keywords)
@@ -37,12 +37,12 @@ module Jsapi
37
37
  ##
38
38
  # :attr_reader: name
39
39
  # The name of the parameter.
40
- attribute :name, writer: false
40
+ attribute :name, read_only: true
41
41
 
42
42
  ##
43
43
  # :attr_reader: schema
44
44
  # The Schema of the parameter.
45
- attribute :schema, writer: false
45
+ attribute :schema, read_only: true
46
46
 
47
47
  # Creates a new parameter.
48
48
  #
@@ -53,7 +53,7 @@ module Jsapi
53
53
  @name = name.to_s
54
54
 
55
55
  keywords = keywords.dup
56
- super(keywords.extract!(:deprecated, :description, :examples, :in))
56
+ super(keywords.extract!(:deprecated, :description, :examples, :in, :openapi_extensions))
57
57
 
58
58
  add_example(value: keywords.delete(:example)) if keywords.key?(:example)
59
59
  keywords[:ref] = keywords.delete(:schema) if keywords.key?(:schema)
@@ -8,7 +8,7 @@ module Jsapi
8
8
  ##
9
9
  # :attr_reader: name
10
10
  # The name of the property.
11
- attribute :name, writer: false
11
+ attribute :name, read_only: true
12
12
 
13
13
  ##
14
14
  # :attr: read_only
@@ -17,7 +17,7 @@ module Jsapi
17
17
  ##
18
18
  # :attr_reader: schema
19
19
  # The Schema of the parameter.
20
- attribute :schema, writer: false
20
+ attribute :schema, read_only: true
21
21
 
22
22
  ##
23
23
  # :attr: source
@@ -26,11 +26,11 @@ module Jsapi
26
26
  ##
27
27
  # :attr_reader: schema
28
28
  # The Schema of the request body.
29
- attribute :schema, writer: false
29
+ attribute :schema, read_only: true
30
30
 
31
31
  def initialize(keywords = {})
32
32
  keywords = keywords.dup
33
- super(keywords.extract!(:content_type, :description, :examples))
33
+ super(keywords.extract!(:content_type, :description, :examples, :openapi_extensions))
34
34
 
35
35
  add_example(value: keywords.delete(:example)) if keywords.key?(:example)
36
36
  keywords[:ref] = keywords.delete(:schema) if keywords.key?(:schema)
@@ -2,31 +2,31 @@
2
2
 
3
3
  module Jsapi
4
4
  module Meta
5
- # Maps a +StandardError+ class to a response status.
6
- class RescueHandler
7
- # The response status.
8
- attr_reader :status
5
+ # Maps an error class to a response status.
6
+ class RescueHandler < Base::Model
7
+ ##
8
+ # :attr: error_class
9
+ # The error class to be mapped.
10
+ attribute :error_class, default: StandardError
9
11
 
10
- # Creates a new rescue handler to map +klass+ to +status+. The default response status
11
- # is <code>"default"</code>.
12
- #
13
- # Raises an +ArgumentError+ if +klass+ isn't a +StandardError+ class.
14
- def initialize(klass, status: nil)
15
- unless klass.is_a?(Class) && klass.ancestors.include?(StandardError)
16
- raise ArgumentError, "#{klass.inspect} must be a standard error class"
17
- end
18
-
19
- @klass = klass
20
- @status = status || 'default'
21
- end
12
+ ##
13
+ # :attr: status
14
+ # The response status. The default is <code>"default"</code>.
15
+ attribute :status, default: 'default'
22
16
 
23
- def inspect # :nodoc:
24
- "#<#{self.class.name} class: #{@klass}, status: #{@status.inspect}>"
17
+ def initialize(keywords = {})
18
+ super
19
+ unless error_class.is_a?(Class)
20
+ raise ArgumentError, "#{error_class.inspect} isn't a class"
21
+ end
22
+ unless error_class <= StandardError
23
+ raise ArgumentError, "#{error_class.inspect} isn't a rescuable class"
24
+ end
25
25
  end
26
26
 
27
27
  # Returns true if +exception+ is an instance of the class to be mapped, false otherwise.
28
28
  def match?(exception)
29
- exception.is_a?(@klass)
29
+ exception.is_a?(error_class)
30
30
  end
31
31
  end
32
32
  end
@@ -41,16 +41,20 @@ module Jsapi
41
41
  ##
42
42
  # :attr_reader: schema
43
43
  # The Schema of the response.
44
- attribute :schema, writer: false
44
+ attribute :schema, read_only: true
45
45
 
46
46
  def initialize(keywords = {})
47
47
  keywords = keywords.dup
48
- super(keywords.extract!(:content_type, :description, :examples, :locale))
49
-
48
+ super(
49
+ keywords.extract!(
50
+ :content_type, :description, :examples, :headers,
51
+ :links, :locale, :openapi_extensions
52
+ )
53
+ )
50
54
  add_example(value: keywords.delete(:example)) if keywords.key?(:example)
51
55
  keywords[:ref] = keywords.delete(:schema) if keywords.key?(:schema)
52
56
 
53
- @schema = Schema.new(**keywords)
57
+ @schema = Schema.new(keywords)
54
58
  end
55
59
 
56
60
  # Returns a hash representing the \OpenAPI response object.
@@ -9,7 +9,7 @@ module Jsapi
9
9
  ##
10
10
  # :attr: schema
11
11
  # The Schema of additional properties.
12
- attribute :schema, Schema, writer: false
12
+ attribute :schema, read_only: true
13
13
 
14
14
  ##
15
15
  # :attr: source
@@ -7,17 +7,19 @@ module Jsapi
7
7
  ##
8
8
  # :attr: items
9
9
  # The Schema defining the kind of items.
10
- attribute :items, Schema, writer: false
10
+ attribute :items, Schema
11
11
 
12
12
  ##
13
13
  # :attr: max_items
14
14
  # The maximum length of an array.
15
- attribute :max_items, writer: false
15
+ attribute :max_items
16
16
 
17
17
  ##
18
18
  # :attr: min_items
19
19
  # The minimum length of an array.
20
- attribute :min_items, writer: false
20
+ attribute :min_items
21
+
22
+ undef items=, max_items=, min_items=
21
23
 
22
24
  def items=(keywords = {}) # :nodoc:
23
25
  if keywords.key?(:schema)
@@ -24,12 +24,12 @@ module Jsapi
24
24
  ##
25
25
  # :attr: enum
26
26
  # The allowed values.
27
- attribute :enum, writer: false
27
+ attribute :enum
28
28
 
29
29
  ##
30
30
  # :attr: examples
31
31
  # The samples matching the schema.
32
- attribute :examples, [::Object]
32
+ attribute :examples, []
33
33
 
34
34
  ##
35
35
  # :attr: external_docs
@@ -53,6 +53,8 @@ module Jsapi
53
53
  # The validations.
54
54
  attr_reader :validations
55
55
 
56
+ undef enum=
57
+
56
58
  # Creates a new schema.
57
59
  def initialize(keywords = {})
58
60
  keywords = keywords.dup
@@ -9,16 +9,18 @@ module Jsapi
9
9
  ##
10
10
  # :attr: maximum
11
11
  # The (exclusive) maximum.
12
- attribute :maximum, writer: false
12
+ attribute :maximum
13
13
 
14
14
  ##
15
15
  # :attr: minimum
16
16
  # The (exclusive) minimum.
17
- attribute :minimum, writer: false
17
+ attribute :minimum
18
18
 
19
19
  ##
20
20
  # :attr: multiple_of
21
- attribute :multiple_of, writer: false
21
+ attribute :multiple_of
22
+
23
+ undef maximum=, minimum=, multiple_of=
22
24
 
23
25
  def maximum=(value) # :nodoc:
24
26
  boundary = Boundary.from(value)
@@ -30,7 +30,9 @@ module Jsapi
30
30
  ##
31
31
  # :attr: properties
32
32
  # The properties.
33
- attribute :properties, { String => Property }, writer: false, default: {}
33
+ attribute :properties, { String => Property }, default: {}
34
+
35
+ undef add_property
34
36
 
35
37
  def add_property(name, keywords = {}) # :nodoc:
36
38
  (@properties ||= {})[name.to_s] = Property.new(name, **keywords)
@@ -64,9 +66,9 @@ module Jsapi
64
66
 
65
67
  value = property.reader.call(object)
66
68
  value = property.default_value(definitions, context: context) if value.nil?
67
- raise "#{discriminator.property_name} can't be blank" if value.blank?
69
+ raise "#{discriminator.property_name} can't be nil" if value.nil?
68
70
 
69
- schema = definitions.schema(discriminator.mapping(value) || value)
71
+ schema = definitions.find_component(:schema, discriminator.mapping(value) || value)
70
72
  raise "inheriting schema couldn't be found: #{value.inspect}" if schema.nil?
71
73
 
72
74
  schema.resolve(definitions).resolve_schema(object, definitions, context: context)
@@ -14,17 +14,19 @@ module Jsapi
14
14
  ##
15
15
  # :attr: max_length
16
16
  # The maximum length of a string.
17
- attribute :max_length, writer: false
17
+ attribute :max_length
18
18
 
19
19
  ##
20
20
  # :attr: min_length
21
21
  # The minimum length of a string.
22
- attribute :min_length, writer: false
22
+ attribute :min_length
23
23
 
24
24
  ##
25
25
  # :attr: pattern
26
26
  # The regular expression a string must match.
27
- attribute :pattern, writer: false
27
+ attribute :pattern
28
+
29
+ undef max_length=, min_length=, pattern=
28
30
 
29
31
  def max_length=(value) # :nodoc:
30
32
  add_validation('max_length', Validation::MaxLength.new(value))
data/lib/jsapi/version.rb CHANGED
@@ -5,6 +5,6 @@ module Jsapi
5
5
  # NOTE: See https://bundler.io/guides/creating_gem.html
6
6
 
7
7
  # The current GEM version.
8
- VERSION = '0.6.1'
8
+ VERSION = '0.7.0'
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Göller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-17 00:00:00.000000000 Z
11
+ date: 2024-09-21 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Jsapi can be used to read requests, produce responses and create OpenAPI
14
14
  documents