praxis 0.22.pre.2 → 2.0.pre.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 (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