praxis 0.22.pre.2 → 2.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +323 -324
  3. data/lib/praxis/action_definition.rb +7 -9
  4. data/lib/praxis/api_definition.rb +27 -44
  5. data/lib/praxis/api_general_info.rb +2 -3
  6. data/lib/praxis/application.rb +14 -141
  7. data/lib/praxis/bootloader.rb +1 -2
  8. data/lib/praxis/bootloader_stages/environment.rb +13 -0
  9. data/lib/praxis/controller.rb +0 -2
  10. data/lib/praxis/dispatcher.rb +4 -6
  11. data/lib/praxis/docs/generator.rb +8 -18
  12. data/lib/praxis/docs/link_builder.rb +1 -1
  13. data/lib/praxis/error_handler.rb +5 -5
  14. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +1 -1
  15. data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +125 -0
  16. data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +16 -18
  17. data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +5 -5
  18. data/lib/praxis/extensions/field_selection.rb +1 -12
  19. data/lib/praxis/extensions/rendering.rb +1 -1
  20. data/lib/praxis/file_group.rb +1 -1
  21. data/lib/praxis/handlers/xml.rb +1 -1
  22. data/lib/praxis/mapper/active_model_compat.rb +63 -0
  23. data/lib/praxis/mapper/resource.rb +242 -0
  24. data/lib/praxis/mapper/selector_generator.rb +126 -0
  25. data/lib/praxis/mapper/sequel_compat.rb +37 -0
  26. data/lib/praxis/middleware_app.rb +13 -15
  27. data/lib/praxis/multipart/part.rb +3 -5
  28. data/lib/praxis/plugins/mapper_plugin.rb +50 -0
  29. data/lib/praxis/request.rb +14 -7
  30. data/lib/praxis/request_stages/response.rb +2 -3
  31. data/lib/praxis/resource_definition.rb +10 -14
  32. data/lib/praxis/response.rb +6 -5
  33. data/lib/praxis/response_definition.rb +5 -7
  34. data/lib/praxis/response_template.rb +3 -4
  35. data/lib/praxis/responses/http.rb +36 -0
  36. data/lib/praxis/responses/internal_server_error.rb +12 -3
  37. data/lib/praxis/responses/multipart_ok.rb +11 -4
  38. data/lib/praxis/responses/validation_error.rb +10 -1
  39. data/lib/praxis/router.rb +3 -3
  40. data/lib/praxis/tasks/api_docs.rb +2 -10
  41. data/lib/praxis/tasks/routes.rb +0 -1
  42. data/lib/praxis/version.rb +1 -1
  43. data/lib/praxis.rb +13 -9
  44. data/praxis.gemspec +2 -3
  45. data/spec/functional_spec.rb +0 -1
  46. data/spec/praxis/action_definition_spec.rb +15 -26
  47. data/spec/praxis/api_definition_spec.rb +8 -13
  48. data/spec/praxis/api_general_info_spec.rb +8 -3
  49. data/spec/praxis/application_spec.rb +7 -13
  50. data/spec/praxis/handlers/xml_spec.rb +2 -2
  51. data/spec/praxis/mapper/resource_spec.rb +169 -0
  52. data/spec/praxis/mapper/selector_generator_spec.rb +301 -0
  53. data/spec/praxis/middleware_app_spec.rb +15 -9
  54. data/spec/praxis/request_spec.rb +7 -17
  55. data/spec/praxis/request_stages/validate_spec.rb +1 -1
  56. data/spec/praxis/resource_definition_spec.rb +10 -12
  57. data/spec/praxis/response_definition_spec.rb +5 -22
  58. data/spec/praxis/response_spec.rb +5 -12
  59. data/spec/praxis/responses/internal_server_error_spec.rb +5 -2
  60. data/spec/praxis/router_spec.rb +4 -8
  61. data/spec/spec_app/app/models/person.rb +3 -3
  62. data/spec/spec_app/config/environment.rb +3 -21
  63. data/spec/spec_app/config.ru +6 -1
  64. data/spec/spec_helper.rb +2 -17
  65. data/spec/support/spec_resources.rb +131 -0
  66. metadata +19 -31
  67. data/lib/praxis/extensions/attribute_filtering/query_builder.rb +0 -39
  68. data/lib/praxis/extensions/attribute_filtering.rb +0 -28
  69. data/lib/praxis/extensions/mapper_selectors.rb +0 -16
  70. data/lib/praxis/media_type_collection.rb +0 -127
  71. data/lib/praxis/plugins/praxis_mapper_plugin.rb +0 -246
  72. data/spec/praxis/media_type_collection_spec.rb +0 -157
  73. data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +0 -142
@@ -0,0 +1,126 @@
1
+ module Praxis::Mapper
2
+ # Generates a set of selectors given a resource and
3
+ # list of resource attributes.
4
+ class SelectorGenerator
5
+ attr_reader :selectors
6
+
7
+ def initialize
8
+ @selectors = Hash.new do |hash, key|
9
+ hash[key] = {select: Set.new, track: Set.new}
10
+ end
11
+ @seen = Hash.new do |hash, resource|
12
+ hash[resource] = Set.new
13
+ end
14
+ end
15
+
16
+ def add(resource, fields)
17
+ return if @seen[resource].include? fields
18
+ @seen[resource] << fields
19
+
20
+ fields.each do |name, field|
21
+ map_property(resource, name, field)
22
+ end
23
+ end
24
+
25
+ def select_all(resource)
26
+ selectors[resource.model][:select] = true
27
+ end
28
+
29
+ def map_property(resource, name, fields)
30
+ if resource.properties.key?(name)
31
+ add_property(resource, name, fields)
32
+ elsif resource.model._praxis_associations.key?(name)
33
+ add_association(resource, name, fields)
34
+ else
35
+ add_select(resource, name)
36
+ end
37
+ end
38
+
39
+ def add_select(resource, name)
40
+ return select_all(resource) if name == :*
41
+ return if selectors[resource.model][:select] == true
42
+
43
+ selectors[resource.model][:select] << name
44
+ end
45
+
46
+ def add_track(resource, name)
47
+ selectors[resource.model][:track] << name
48
+ end
49
+
50
+ def add_association(resource, name, fields)
51
+ association = resource.model._praxis_associations.fetch(name) do
52
+ raise "missing association for #{resource} with name #{name}"
53
+ end
54
+ associated_resource = resource.model_map[association[:model]]
55
+
56
+ case association[:type]
57
+ when :many_to_one
58
+ add_track(resource, name)
59
+ Array(association[:key]).each do |akey|
60
+ add_select(resource, akey)
61
+ end
62
+ when :one_to_many
63
+ add_track(resource, name)
64
+ Array(association[:key]).each do |akey|
65
+ add_select(associated_resource, akey)
66
+ end
67
+ when :many_to_many
68
+ # If we haven't explicitly added the "through" option in the association
69
+ # then we'll assume the underlying ORM is able to fill in the gap. We will
70
+ # simply add the fields for the associated resource below
71
+ if association.key? :through
72
+ head, *tail = association[:through]
73
+ new_fields = tail.reverse.inject(fields) do |thing, step|
74
+ {step => thing}
75
+ end
76
+ return add_association(resource, head, new_fields)
77
+ else
78
+ add_track(resource, name)
79
+ end
80
+ else
81
+ raise "no select applicable for #{association[:type].inspect}"
82
+ end
83
+
84
+ unless fields == true
85
+ # recurse into the field
86
+ add(associated_resource,fields)
87
+ end
88
+ end
89
+
90
+ def add_property(resource, name, fields)
91
+ dependencies = resource.properties[name][:dependencies]
92
+ if dependencies
93
+ dependencies.each do |dependency|
94
+ # if dependency includes the name, then map it directly as the field
95
+ if dependency == name
96
+ add_select(resource, name)
97
+ else
98
+ apply_dependency(resource, dependency)
99
+ end
100
+ end
101
+ end
102
+
103
+ head, *tail = resource.properties[name][:through]
104
+ return if head.nil?
105
+
106
+ new_fields = tail.reverse.inject(fields) do |thing, step|
107
+ {step => thing}
108
+ end
109
+
110
+ add_association(resource, head, new_fields)
111
+ end
112
+
113
+ def apply_dependency(resource, dependency)
114
+ case dependency
115
+ when Symbol
116
+ map_property(resource, dependency, {})
117
+ when String
118
+ head, tail = dependency.split('.').collect(&:to_sym)
119
+ raise "String dependencies can not be singular" if tail.nil?
120
+
121
+ add_association(resource, head, {tail => true})
122
+ end
123
+ end
124
+
125
+ end
126
+ end
@@ -0,0 +1,37 @@
1
+ require 'active_support/concern'
2
+
3
+ module Praxis::Mapper
4
+ module SequelCompat
5
+ extend ActiveSupport::Concern
6
+
7
+ `` included do
8
+ attr_accessor :_resource
9
+ end
10
+
11
+ module ClassMethods
12
+ def _filter_query_builder_class
13
+ Praxis::Extensions::SequelFilterQueryBuilder
14
+ end
15
+
16
+ def _praxis_associations
17
+ orig = self.association_reflections.clone
18
+
19
+ orig.each do |k,v|
20
+ v[:model] = v.associated_class
21
+ if v.respond_to?(:primary_key)
22
+ v[:primary_key] = v.primary_key
23
+ else
24
+ # FIXME: figure out exactly what to do here.
25
+ # not super critical, as we can't track these associations
26
+ # directly, but it would be nice to traverse these
27
+ # properly.
28
+ v[:primary_key] = :unsupported
29
+ end
30
+ end
31
+ orig
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -2,39 +2,37 @@ module Praxis
2
2
  class MiddlewareApp
3
3
 
4
4
  attr_reader :target
5
+
5
6
  # Initialize the application instance with the desired args, and return the wrapping class.
6
7
  def self.for( **args )
7
8
  Class.new(self) do
8
- class << self
9
- attr_accessor :app_instance
10
- attr_reader :app_name, :skip_registration
11
- end
12
- @app_name = args.delete(:name)
13
- @skip_registration = args.delete(:skip_registration) || false
14
9
  @args = args
15
- @app_instance = nil
16
-
10
+ @setup_done = false
17
11
  def self.name
18
12
  'MiddlewareApp'
19
13
  end
20
14
  def self.args
21
15
  @args
22
16
  end
17
+ def self.setup_done
18
+ @setup_done
19
+ end
23
20
  def self.setup
24
- app_instance.setup(**args)
21
+ @setup_done = true
22
+ Praxis::Application.instance.setup(**@args)
25
23
  end
26
24
  end
27
- end
25
+ end
28
26
 
29
27
  def initialize( inner )
30
28
  @target = inner
31
- self.class.app_instance = Praxis::Application.new(name: self.class.app_name, skip_registration: self.class.skip_registration)
29
+ @setup_done = false
32
30
  end
33
-
31
+
34
32
  def call(env)
35
- # NOTE: Need to make sure somebody has properly called the setup above before this is called
36
- #@app_instance ||= Praxis::Application.new.setup(**self.class.args) #I Think that's not right at all...
37
- result = self.class.app_instance.call(env)
33
+ self.class.setup unless self.class.setup_done
34
+
35
+ result = Praxis::Application.instance.call(env)
38
36
 
39
37
  unless ( [404,405].include?(result[0].to_i) && result[1]['X-Cascade'] == 'pass' )
40
38
  # Respect X-Cascade header if it doesn't specify 'pass'
@@ -10,7 +10,6 @@ module Praxis
10
10
  attr_accessor :headers_attribute
11
11
  attr_accessor :filename_attribute
12
12
  attr_accessor :default_handler
13
- attr_accessor :application
14
13
 
15
14
  def self.check_option!(name, definition)
16
15
  case name
@@ -78,8 +77,7 @@ module Praxis
78
77
  @name = name
79
78
  @body = body
80
79
  @headers = headers
81
- @application = Praxis::Application.current_instance
82
- @default_handler = application.handlers['json']
80
+ @default_handler = Praxis::Application.instance.handlers['json']
83
81
 
84
82
  if content_type.nil?
85
83
  self.content_type = 'text/plain'
@@ -214,7 +212,7 @@ module Praxis
214
212
  end
215
213
 
216
214
  def handler
217
- handlers = application.handlers
215
+ handlers = Praxis::Application.instance.handlers
218
216
  (content_type && handlers[content_type.handler_name]) || @default_handler
219
217
  end
220
218
 
@@ -251,7 +249,7 @@ module Praxis
251
249
 
252
250
  # and return that one if it already corresponds to a registered handler
253
251
  # otherwise, add the encoding
254
- if application.handlers.include?(pick.handler_name)
252
+ if Praxis::Application.instance.handlers.include?(pick.handler_name)
255
253
  return pick
256
254
  else
257
255
  return pick + handler_name
@@ -0,0 +1,50 @@
1
+ require 'singleton'
2
+ require 'praxis/extensions/attribute_filtering/filtering_params'
3
+
4
+ module Praxis
5
+ module Plugins
6
+ module MapperPlugin
7
+ include Praxis::PluginConcern
8
+
9
+ class Plugin < Praxis::Plugin
10
+ include Singleton
11
+ end
12
+
13
+ module Controller
14
+ extend ActiveSupport::Concern
15
+
16
+ included do
17
+ include Praxis::Extensions::FieldExpansion
18
+ end
19
+
20
+ def set_selectors
21
+ return unless self.media_type.respond_to?(:domain_model) &&
22
+ self.media_type.domain_model < Praxis::Mapper::Resource
23
+
24
+ resolved = Praxis::MediaType::FieldResolver.resolve(self.media_type, self.expanded_fields)
25
+ selector_generator.add(self.media_type.domain_model, resolved)
26
+ end
27
+
28
+ def build_query(base_query) # rubocop:disable Metrics/AbcSize
29
+ domain_model = self.media_type&.domain_model
30
+ raise "No domain model defined for #{self.name}. Cannot use the attribute filtering helpers without it" unless domain_model
31
+
32
+ filters = request.params.filters if request.params&.respond_to?(:filters)
33
+ base_query = domain_model.craft_filter_query( base_query , filters: filters )
34
+
35
+ resolved = Praxis::MediaType::FieldResolver.resolve(self.media_type, self.expanded_fields)
36
+ base_query = domain_model.craft_field_selection_query(base_query, selectors: selector_generator.selectors, resolved: resolved)
37
+
38
+ # TODO: handle pagination and ordering
39
+ base_query
40
+ end
41
+
42
+ def selector_generator
43
+ @selector_generator ||= Praxis::Mapper::SelectorGenerator.new
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -1,7 +1,7 @@
1
1
  module Praxis
2
2
 
3
3
  class Request < Praxis.request_superclass
4
- attr_reader :env, :query, :praxis_instance
4
+ attr_reader :env, :query
5
5
  attr_accessor :route_params, :action
6
6
 
7
7
  PATH_VERSION_PREFIX = "/v".freeze
@@ -14,11 +14,10 @@ module Praxis
14
14
  API_NO_VERSION_NAME = 'n/a'.freeze
15
15
  VERSION_USING_DEFAULTS = [:header, :params].freeze
16
16
 
17
- def initialize(env, args={})
17
+ def initialize(env)
18
18
  @env = env
19
19
  @query = Rack::Utils.parse_nested_query(env[QUERY_STRING_NAME])
20
20
  @route_params = {}
21
- @praxis_instance = args[:application]
22
21
  end
23
22
 
24
23
  # Determine the content type of this request as indicated by the Content-Type header.
@@ -87,8 +86,8 @@ module Praxis
87
86
  PATH_VERSION_MATCHER = %r{^#{self.path_version_prefix}(?<version>[^\/]+)\/}.freeze
88
87
 
89
88
  def path_version_matcher
90
- if praxis_instance.versioning_scheme == :path
91
- matcher = Mustermann.new(praxis_instance.api_definition.info.base_path + '*')
89
+ if Application.instance.versioning_scheme == :path
90
+ matcher = Mustermann.new(ApiDefinition.instance.info.base_path + '*')
92
91
  matcher.params(self.path)[API_VERSION_PARAM_NAME]
93
92
  else
94
93
  PATH_VERSION_MATCHER.match(self.path)[:version]
@@ -97,7 +96,8 @@ module Praxis
97
96
 
98
97
  def version
99
98
  result = nil
100
- Array(praxis_instance.versioning_scheme).find do |mode|
99
+
100
+ Array(Application.instance.versioning_scheme).find do |mode|
101
101
  case mode
102
102
  when :header;
103
103
  result = env[API_VERSION_HEADER_NAME]
@@ -128,7 +128,8 @@ module Praxis
128
128
  def load_payload(context)
129
129
  return unless action.payload
130
130
  return if content_type.nil?
131
- raw = if (handler = praxis_instance.handlers[content_type.handler_name])
131
+
132
+ raw = if (handler = Praxis::Application.instance.handlers[content_type.handler_name])
132
133
  handler.parse(self.raw_payload)
133
134
  else
134
135
  # TODO is this a good default?
@@ -161,6 +162,12 @@ module Praxis
161
162
  @unmatched_versions ||= Set.new
162
163
  end
163
164
 
165
+ # Override the inspect instance method of a request, as, by default, the kernel inspect will go nuts
166
+ # traversing the action and app_instance and therefore all associated instance variables reachable through that
167
+ def inspect
168
+ "'@env' => #{@env.inspect},\n'@headers' => #{@headers.inspect},\n'@params' => #{@params.inspect},\n'@query' => #{@query.inspect}"
169
+ end
170
+
164
171
  end
165
172
 
166
173
  end
@@ -8,9 +8,8 @@ module Praxis
8
8
 
9
9
  response.handle
10
10
 
11
- config = Application.current_instance.config
12
- if config.praxis.validate_responses == true
13
- validate_body = config.praxis.validate_response_bodies
11
+ if Application.instance.config.praxis.validate_responses == true
12
+ validate_body = Application.instance.config.praxis.validate_response_bodies
14
13
 
15
14
  response.validate(action, validate_body: validate_body)
16
15
  end
@@ -8,15 +8,11 @@ module Praxis
8
8
  DEFAULT_RESOURCE_HREF_ACTION = :show
9
9
 
10
10
  included do
11
- # Store the attached (i.e., current) Praxis App instance into the resource definition for easy retrieval later
12
- @application = Application.instance
13
- @application.resource_definitions << self
14
-
15
11
  @version = 'n/a'.freeze
16
12
  @actions = Hash.new
17
13
  @responses = Hash.new
18
14
 
19
- @action_defaults = Trait.new &ResourceDefinition.generate_defaults_block(application: @application)
15
+ @action_defaults = Trait.new &ResourceDefinition.generate_defaults_block
20
16
 
21
17
  @version_options = {}
22
18
  @metadata = {}
@@ -37,12 +33,13 @@ module Praxis
37
33
 
38
34
  @on_finalize = Array.new
39
35
 
40
-
36
+ Application.instance.resource_definitions << self
41
37
  end
42
38
 
43
- def self.generate_defaults_block( version: nil, application:)
39
+ def self.generate_defaults_block( version: nil )
40
+
44
41
  # Ensure we inherit any base params defined in the API definition for the passed in version
45
- base_attributes = if (base_params = application.api_definition.info(version).base_params)
42
+ base_attributes = if (base_params = ApiDefinition.instance.info(version).base_params)
46
43
  base_params.attributes
47
44
  else
48
45
  {}
@@ -59,8 +56,8 @@ module Praxis
59
56
  end
60
57
  end
61
58
 
62
- def self.finalize!(application: )
63
- application.resource_definitions.each do |resource_definition|
59
+ def self.finalize!
60
+ Application.instance.resource_definitions.each do |resource_definition|
64
61
  while (block = resource_definition.on_finalize.shift)
65
62
  block.call
66
63
  end
@@ -76,7 +73,6 @@ module Praxis
76
73
  attr_reader :traits
77
74
  attr_reader :version_prefix
78
75
  attr_reader :parent_prefix
79
- attr_reader :application
80
76
 
81
77
  # opaque hash of user-defined medata, used to decorate the definition,
82
78
  # and also available in the generated JSON documents
@@ -203,7 +199,7 @@ module Praxis
203
199
  end
204
200
  end
205
201
 
206
- @action_defaults.instance_eval &ResourceDefinition.generate_defaults_block( version: version, application: self.application)
202
+ @action_defaults.instance_eval &ResourceDefinition.generate_defaults_block( version: version )
207
203
  end
208
204
 
209
205
 
@@ -242,10 +238,10 @@ module Praxis
242
238
  end
243
239
 
244
240
  def trait(trait_name)
245
- unless self.application.api_definition.traits.has_key? trait_name
241
+ unless ApiDefinition.instance.traits.has_key? trait_name
246
242
  raise Exceptions::InvalidTrait.new("Trait #{trait_name} not found in the system")
247
243
  end
248
- trait = self.application.api_definition.traits.fetch(trait_name)
244
+ trait = ApiDefinition.instance.traits.fetch(trait_name)
249
245
  @traits << trait_name
250
246
  end
251
247
  alias_method :use, :trait
@@ -63,23 +63,24 @@ module Praxis
63
63
  self.class.response_name
64
64
  end
65
65
 
66
- def format!(config:)
66
+ def format!
67
67
  end
68
68
 
69
- def encode!(handlers:)
69
+ def encode!
70
70
  case @body
71
71
  when Hash, Array
72
72
  # response payload is structured data; transform it into an entity using the handler
73
73
  # implied by the response's media type. If no handler is registered for this
74
74
  # name, assume JSON as a default handler.
75
+ handlers = Praxis::Application.instance.handlers
75
76
  handler = (content_type && handlers[content_type.handler_name]) || handlers['json']
76
77
  @body = handler.generate(@body)
77
78
  end
78
79
  end
79
80
 
80
- def finish(application:)
81
- format!(config: application.config)
82
- encode!(handlers: application.handlers)
81
+ def finish
82
+ format!
83
+ encode!
83
84
 
84
85
  @body = Array(@body)
85
86
 
@@ -4,9 +4,8 @@ module Praxis
4
4
 
5
5
  class ResponseDefinition
6
6
  attr_reader :name
7
- attr_reader :application
8
-
9
- def initialize(response_name, application, **spec, &block)
7
+
8
+ def initialize(response_name, **spec, &block)
10
9
  unless response_name
11
10
  raise Exceptions::InvalidConfiguration.new(
12
11
  "Response name is required for a response specification"
@@ -14,7 +13,6 @@ module Praxis
14
13
  end
15
14
  @spec = { headers:{} }
16
15
  @name = response_name
17
- @application = application
18
16
  self.instance_exec(**spec, &block) if block_given?
19
17
 
20
18
  if self.status.nil?
@@ -143,9 +141,9 @@ module Praxis
143
141
  # FIXME: remove load when when MediaTypeCommon.identifier returns a MediaTypeIdentifier
144
142
  identifier = MediaTypeIdentifier.load(self.media_type.identifier)
145
143
 
146
- default_handlers = application.api_definition.info.produces
144
+ default_handlers = ApiDefinition.instance.info.produces
147
145
 
148
- handlers = application.handlers.select do |k,v|
146
+ handlers = Praxis::Application.instance.handlers.select do |k,v|
149
147
  default_handlers.include?(k)
150
148
  end
151
149
 
@@ -202,7 +200,7 @@ module Praxis
202
200
  raise ArgumentError, "Parts definition for response #{name} does not allow :like and a block simultaneously"
203
201
  end
204
202
  if like
205
- template = application.api_definition.response(like)
203
+ template = ApiDefinition.instance.response(like)
206
204
  @parts = template.compile(nil, **args)
207
205
  else # block
208
206
  @parts = Praxis::ResponseDefinition.new('anonymous', **args, &a_proc)
@@ -1,12 +1,11 @@
1
1
  module Praxis
2
2
 
3
3
  class ResponseTemplate
4
- attr_reader :name, :block, :application
4
+ attr_reader :name, :block
5
5
 
6
- def initialize(response_name, application, &block)
6
+ def initialize(response_name, &block)
7
7
  @name = response_name
8
8
  @block = block
9
- @application = application
10
9
  end
11
10
 
12
11
  def compile(action=nil, **args)
@@ -24,7 +23,7 @@ module Praxis
24
23
  args[:media_type] = media_type
25
24
  end
26
25
  end
27
- Praxis::ResponseDefinition.new(name, application, **args, &block)
26
+ Praxis::ResponseDefinition.new(name, **args, &block)
28
27
  end
29
28
 
30
29
  def describe
@@ -131,5 +131,41 @@ module Praxis
131
131
  self.status = 422
132
132
  end
133
133
 
134
+ ApiDefinition.define do |api|
135
+
136
+
137
+ [
138
+ [ :accepted, 202, "The request has been accepted for processing, but the processing has not been completed." ],
139
+ [ :no_content, 204,"The server successfully processed the request, but is not returning any content."],
140
+ [ :multiple_choices, 300,"Indicates multiple options for the resource that the client may follow."],
141
+ [ :moved_permanently, 301,"This and all future requests should be directed to the given URI."],
142
+ [ :found, 302,"The requested resource resides temporarily under a different URI."],
143
+ [ :see_other, 303,"The response to the request can be found under another URI using a GET method"],
144
+ [ :not_modified, 304,"Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-Match."],
145
+ [ :temporary_redirect, 307,"In this case, the request should be repeated with another URI; however, future requests should still use the original URI."],
146
+ [ :bad_request, 400,"The request cannot be fulfilled due to bad syntax."],
147
+ [ :unauthorized, 401,"Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided."],
148
+ [ :forbidden, 403,"The request was a valid request, but the server is refusing to respond to it."],
149
+ [ :not_found, 404,"The requested resource could not be found but may be available again in the future."],
150
+ [ :method_not_allowed, 405,"A request was made of a resource using a request method not supported by that resource."],
151
+ [ :not_acceptable, 406,"The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request."],
152
+ [ :request_timeout, 408,"The server timed out waiting for the request."],
153
+ [ :conflict, 409, "Indicates that the request could not be processed because of conflict in the request, such as an edit conflict in the case of multiple updates."],
154
+ [ :precondition_failed, 412,"The server does not meet one of the preconditions that the requester put on the request."],
155
+ [ :unprocessable_entity, 422,"The request was well-formed but was unable to be followed due to semantic errors."],
156
+ ].each do |name, code, base_description|
157
+ api.response_template name do |media_type: nil, location: nil, headers: nil, description: nil|
158
+ status code
159
+ description( description || base_description ) # description can "potentially" be overriden in an individual action.
160
+
161
+ media_type media_type if media_type
162
+ location location if location
163
+ headers headers if headers
164
+ end
165
+ end
166
+
167
+ end
168
+
169
+
134
170
  end
135
171
  end
@@ -13,15 +13,16 @@ module Praxis
13
13
  @error = error
14
14
  end
15
15
 
16
- def format!(exception = @error, config:)
16
+ def format!(exception = @error)
17
17
  if @error
18
- if config.praxis.show_exceptions == true
18
+
19
+ if Application.instance.config.praxis.show_exceptions == true
19
20
  msg = {
20
21
  name: exception.class.name,
21
22
  message: exception.message,
22
23
  backtrace: exception.backtrace
23
24
  }
24
- msg[:cause] = format!(exception.cause, config: config) if exception.cause
25
+ msg[:cause] = format!(exception.cause) if exception.cause
25
26
  else
26
27
  msg = {name: 'InternalServerError', message: "Something bad happened."}
27
28
  end
@@ -33,4 +34,12 @@ module Praxis
33
34
 
34
35
  end
35
36
 
37
+ ApiDefinition.define do |api|
38
+ api.response_template :internal_server_error do
39
+ description "A generic error message, given when an unexpected condition was encountered and no more specific message is suitable."
40
+ status 500
41
+ media_type "application/json"
42
+ end
43
+ end
44
+
36
45
  end
@@ -19,7 +19,7 @@ module Praxis
19
19
  end
20
20
 
21
21
 
22
- def encode!(handlers:)
22
+ def encode!
23
23
  case @body
24
24
  when Praxis::Types::MultipartArray
25
25
  @body = @body.dump
@@ -28,9 +28,9 @@ module Praxis
28
28
  end
29
29
  end
30
30
 
31
- def finish(application:)
32
- format!(config: application.config)
33
- encode!(handlers: application.handlers)
31
+ def finish
32
+ format!
33
+ encode!
34
34
 
35
35
  @body = Array(@body)
36
36
 
@@ -41,4 +41,11 @@ module Praxis
41
41
 
42
42
  end
43
43
 
44
+ ApiDefinition.define do |api|
45
+ api.response_template :multipart_ok do |media_type: Praxis::Types::MultipartArray|
46
+ status 200
47
+ media_type media_type
48
+ end
49
+ end
50
+
44
51
  end
@@ -15,7 +15,7 @@ module Praxis
15
15
  @documentation = documentation
16
16
  end
17
17
 
18
- def format!(**_args)
18
+ def format!
19
19
  @body = {name: 'ValidationError', summary: @summary }
20
20
  @body[:errors] = @errors if @errors
21
21
 
@@ -31,4 +31,13 @@ module Praxis
31
31
 
32
32
  end
33
33
 
34
+
35
+ ApiDefinition.define do |api|
36
+ api.response_template :validation_error do
37
+ description "An error message indicating that one or more elements of the request did not match the API specification for the action"
38
+ status 400
39
+ media_type "application/json"
40
+ end
41
+ end
42
+
34
43
  end