jsonapi-resources 0.7.0 → 0.7.1.beta1
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 +4 -4
- data/README.md +196 -190
- data/lib/generators/jsonapi/USAGE +6 -1
- data/lib/generators/jsonapi/controller_generator.rb +14 -0
- data/lib/generators/jsonapi/templates/jsonapi_controller.rb +4 -0
- data/lib/jsonapi/active_record_operations_processor.rb +4 -3
- data/lib/jsonapi/acts_as_resource_controller.rb +7 -3
- data/lib/jsonapi/error_codes.rb +26 -26
- data/lib/jsonapi/exceptions.rb +124 -53
- data/lib/jsonapi/relationship.rb +8 -0
- data/lib/jsonapi/request.rb +4 -6
- data/lib/jsonapi/resource.rb +37 -13
- data/lib/jsonapi/resource_controller.rb +14 -2
- data/lib/jsonapi/resource_serializer.rb +2 -8
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/routing_ext.rb +1 -1
- data/locales/en.yml +80 -0
- data/test/controllers/controller_test.rb +35 -5
- data/test/fixtures/active_record.rb +11 -8
- data/test/fixtures/comments.yml +1 -1
- data/test/fixtures/preferences.yml +0 -4
- data/test/lib/generators/jsonapi/controller_generator_test.rb +25 -0
- data/test/test_helper.rb +3 -0
- data/test/unit/operation/operations_processor_test.rb +3 -3
- data/test/unit/resource/resource_test.rb +20 -0
- data/test/unit/serializer/serializer_test.rb +0 -6
- metadata +10 -5
| @@ -1,8 +1,13 @@ | |
| 1 1 | 
             
            Description:
         | 
| 2 2 | 
             
                Generator for JSONAPI Resources
         | 
| 3 3 |  | 
| 4 | 
            -
             | 
| 4 | 
            +
            Examples:
         | 
| 5 5 | 
             
                rails generate jsonapi:resource Post
         | 
| 6 6 |  | 
| 7 7 | 
             
                This will create:
         | 
| 8 8 | 
             
                    app/resources/post_resource.rb
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                rails generate jsonapi:controller Post
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                This will create:
         | 
| 13 | 
            +
                    app/controllers/posts_controller.rb
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            module Jsonapi
         | 
| 2 | 
            +
              class ControllerGenerator < Rails::Generators::NamedBase
         | 
| 3 | 
            +
                source_root File.expand_path('../templates', __FILE__)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def create_resource
         | 
| 6 | 
            +
                  template_file = File.join(
         | 
| 7 | 
            +
                    'app/controllers',
         | 
| 8 | 
            +
                    class_path,
         | 
| 9 | 
            +
                    "#{file_name.pluralize}_controller.rb"
         | 
| 10 | 
            +
                  )
         | 
| 11 | 
            +
                  template 'jsonapi_controller.rb', template_file
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
| @@ -18,7 +18,7 @@ class ActiveRecordOperationsProcessor < JSONAPI::OperationsProcessor | |
| 18 18 | 
             
              # Catch errors that should be handled before JSONAPI::Exceptions::Error
         | 
| 19 19 | 
             
              # and other unprocessed exceptions
         | 
| 20 20 | 
             
              def process_operation(operation)
         | 
| 21 | 
            -
                with_default_handling do | 
| 21 | 
            +
                with_default_handling do
         | 
| 22 22 | 
             
                  begin
         | 
| 23 23 | 
             
                    operation.apply
         | 
| 24 24 | 
             
                  rescue ActiveRecord::DeleteRestrictionError => e
         | 
| @@ -26,9 +26,10 @@ class ActiveRecordOperationsProcessor < JSONAPI::OperationsProcessor | |
| 26 26 | 
             
                    return JSONAPI::ErrorsOperationResult.new(record_locked_error.errors[0].code, record_locked_error.errors)
         | 
| 27 27 |  | 
| 28 28 | 
             
                  rescue ActiveRecord::RecordNotFound
         | 
| 29 | 
            -
                     | 
| 29 | 
            +
                    key = operation.respond_to?(:associated_key) ? operation.associated_key : :unavailable
         | 
| 30 | 
            +
                    record_not_found = JSONAPI::Exceptions::RecordNotFound.new(key)
         | 
| 30 31 | 
             
                    return JSONAPI::ErrorsOperationResult.new(record_not_found.errors[0].code, record_not_found.errors)
         | 
| 31 32 | 
             
                  end
         | 
| 32 | 
            -
                end | 
| 33 | 
            +
                end
         | 
| 33 34 | 
             
              end
         | 
| 34 35 | 
             
            end
         | 
| @@ -179,9 +179,13 @@ module JSONAPI | |
| 179 179 | 
             
                  when JSONAPI::Exceptions::Error
         | 
| 180 180 | 
             
                    render_errors(e.errors)
         | 
| 181 181 | 
             
                  else
         | 
| 182 | 
            -
                     | 
| 183 | 
            -
             | 
| 184 | 
            -
                     | 
| 182 | 
            +
                    if JSONAPI.configuration.exception_class_whitelist.any? { |k| e.class.ancestors.include?(k) }
         | 
| 183 | 
            +
                      fail e
         | 
| 184 | 
            +
                    else
         | 
| 185 | 
            +
                      internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
         | 
| 186 | 
            +
                      Rails.logger.error { "Internal Server Error: #{e.message} #{e.backtrace.join("\n")}" }
         | 
| 187 | 
            +
                      render_errors(internal_server_error.errors)
         | 
| 188 | 
            +
                    end
         | 
| 185 189 | 
             
                  end
         | 
| 186 190 | 
             
                end
         | 
| 187 191 |  | 
    
        data/lib/jsonapi/error_codes.rb
    CHANGED
    
    | @@ -1,30 +1,30 @@ | |
| 1 1 | 
             
            module JSONAPI
         | 
| 2 | 
            -
              VALIDATION_ERROR = 100
         | 
| 3 | 
            -
              INVALID_RESOURCE = 101
         | 
| 4 | 
            -
              FILTER_NOT_ALLOWED = 102
         | 
| 5 | 
            -
              INVALID_FIELD_VALUE = 103
         | 
| 6 | 
            -
              INVALID_FIELD = 104
         | 
| 7 | 
            -
              PARAM_NOT_ALLOWED = 105
         | 
| 8 | 
            -
              PARAM_MISSING = 106
         | 
| 9 | 
            -
              INVALID_FILTER_VALUE = 107
         | 
| 10 | 
            -
              COUNT_MISMATCH = 108
         | 
| 11 | 
            -
              KEY_ORDER_MISMATCH = 109
         | 
| 12 | 
            -
              KEY_NOT_INCLUDED_IN_URL = 110
         | 
| 13 | 
            -
              INVALID_INCLUDE = 112
         | 
| 14 | 
            -
              RELATION_EXISTS = 113
         | 
| 15 | 
            -
              INVALID_SORT_CRITERIA = 114
         | 
| 16 | 
            -
              INVALID_LINKS_OBJECT = 115
         | 
| 17 | 
            -
              TYPE_MISMATCH = 116
         | 
| 18 | 
            -
              INVALID_PAGE_OBJECT = 117
         | 
| 19 | 
            -
              INVALID_PAGE_VALUE = 118
         | 
| 20 | 
            -
              INVALID_FIELD_FORMAT = 119
         | 
| 21 | 
            -
              INVALID_FILTERS_SYNTAX = 120
         | 
| 22 | 
            -
              SAVE_FAILED = 121
         | 
| 23 | 
            -
              FORBIDDEN = 403
         | 
| 24 | 
            -
              RECORD_NOT_FOUND = 404
         | 
| 25 | 
            -
              UNSUPPORTED_MEDIA_TYPE = 415
         | 
| 26 | 
            -
              LOCKED = 423
         | 
| 27 | 
            -
              INTERNAL_SERVER_ERROR = 500
         | 
| 2 | 
            +
              VALIDATION_ERROR = '100'
         | 
| 3 | 
            +
              INVALID_RESOURCE = '101'
         | 
| 4 | 
            +
              FILTER_NOT_ALLOWED = '102'
         | 
| 5 | 
            +
              INVALID_FIELD_VALUE = '103'
         | 
| 6 | 
            +
              INVALID_FIELD = '104'
         | 
| 7 | 
            +
              PARAM_NOT_ALLOWED = '105'
         | 
| 8 | 
            +
              PARAM_MISSING = '106'
         | 
| 9 | 
            +
              INVALID_FILTER_VALUE = '107'
         | 
| 10 | 
            +
              COUNT_MISMATCH = '108'
         | 
| 11 | 
            +
              KEY_ORDER_MISMATCH = '109'
         | 
| 12 | 
            +
              KEY_NOT_INCLUDED_IN_URL = '110'
         | 
| 13 | 
            +
              INVALID_INCLUDE = '112'
         | 
| 14 | 
            +
              RELATION_EXISTS = '113'
         | 
| 15 | 
            +
              INVALID_SORT_CRITERIA = '114'
         | 
| 16 | 
            +
              INVALID_LINKS_OBJECT = '115'
         | 
| 17 | 
            +
              TYPE_MISMATCH = '116'
         | 
| 18 | 
            +
              INVALID_PAGE_OBJECT = '117'
         | 
| 19 | 
            +
              INVALID_PAGE_VALUE = '118'
         | 
| 20 | 
            +
              INVALID_FIELD_FORMAT = '119'
         | 
| 21 | 
            +
              INVALID_FILTERS_SYNTAX = '120'
         | 
| 22 | 
            +
              SAVE_FAILED = '121'
         | 
| 23 | 
            +
              FORBIDDEN = '403'
         | 
| 24 | 
            +
              RECORD_NOT_FOUND = '404'
         | 
| 25 | 
            +
              UNSUPPORTED_MEDIA_TYPE = '415'
         | 
| 26 | 
            +
              LOCKED = '423'
         | 
| 27 | 
            +
              INTERNAL_SERVER_ERROR = '500'
         | 
| 28 28 |  | 
| 29 29 | 
             
              TEXT_ERRORS =
         | 
| 30 30 | 
             
                { VALIDATION_ERROR => 'VALIDATION_ERROR',
         | 
    
        data/lib/jsonapi/exceptions.rb
    CHANGED
    
    | @@ -18,8 +18,10 @@ module JSONAPI | |
| 18 18 |  | 
| 19 19 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::INTERNAL_SERVER_ERROR,
         | 
| 20 20 | 
             
                                        status: :internal_server_error,
         | 
| 21 | 
            -
                                        title: ' | 
| 22 | 
            -
             | 
| 21 | 
            +
                                        title: I18n.t('jsonapi-resources.exceptions.internal_server_error.title', 
         | 
| 22 | 
            +
                                                      default: 'Internal Server Error'),
         | 
| 23 | 
            +
                                        detail: I18n.t('jsonapi-resources.exceptions.internal_server_error.detail', 
         | 
| 24 | 
            +
                                                       default: 'Internal Server Error'),
         | 
| 23 25 | 
             
                                        meta: meta)]
         | 
| 24 26 | 
             
                  end
         | 
| 25 27 | 
             
                end
         | 
| @@ -33,8 +35,10 @@ module JSONAPI | |
| 33 35 | 
             
                  def errors
         | 
| 34 36 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::INVALID_RESOURCE,
         | 
| 35 37 | 
             
                                        status: :bad_request,
         | 
| 36 | 
            -
                                        title: ' | 
| 37 | 
            -
             | 
| 38 | 
            +
                                        title: I18n.t('jsonapi-resources.exceptions.invalid_resource.title', 
         | 
| 39 | 
            +
                                                      default: 'Invalid resource'),
         | 
| 40 | 
            +
                                        detail: I18n.t('jsonapi-resources.exceptions.invalid_resource.detail', 
         | 
| 41 | 
            +
                                                       default: "#{resource} is not a valid resource.", resource: resource))]
         | 
| 38 42 | 
             
                  end
         | 
| 39 43 | 
             
                end
         | 
| 40 44 |  | 
| @@ -47,8 +51,10 @@ module JSONAPI | |
| 47 51 | 
             
                  def errors
         | 
| 48 52 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::RECORD_NOT_FOUND,
         | 
| 49 53 | 
             
                                        status: :not_found,
         | 
| 50 | 
            -
                                        title: ' | 
| 51 | 
            -
             | 
| 54 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.record_not_found.title', 
         | 
| 55 | 
            +
                                                              default: 'Record not found'),
         | 
| 56 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.record_not_found.detail', 
         | 
| 57 | 
            +
                                                               default: "The record identified by #{id} could not be found.", id: id))]
         | 
| 52 58 | 
             
                  end
         | 
| 53 59 | 
             
                end
         | 
| 54 60 |  | 
| @@ -61,8 +67,12 @@ module JSONAPI | |
| 61 67 | 
             
                  def errors
         | 
| 62 68 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::UNSUPPORTED_MEDIA_TYPE,
         | 
| 63 69 | 
             
                                        status: :unsupported_media_type,
         | 
| 64 | 
            -
                                        title: ' | 
| 65 | 
            -
             | 
| 70 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.unsupported_media_type.title', 
         | 
| 71 | 
            +
                                                              default: 'Unsupported media type'),
         | 
| 72 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.unsupported_media_type.detail',
         | 
| 73 | 
            +
                                                               default: "All requests that create or update must use the '#{JSONAPI::MEDIA_TYPE}' Content-Type. This request specified '#{media_type}'.",
         | 
| 74 | 
            +
                                                               needed_media_type: JSONAPI::MEDIA_TYPE,
         | 
| 75 | 
            +
                                                               media_type: media_type))]
         | 
| 66 76 | 
             
                  end
         | 
| 67 77 | 
             
                end
         | 
| 68 78 |  | 
| @@ -75,8 +85,11 @@ module JSONAPI | |
| 75 85 | 
             
                  def errors
         | 
| 76 86 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::RELATION_EXISTS,
         | 
| 77 87 | 
             
                                        status: :bad_request,
         | 
| 78 | 
            -
                                        title: ' | 
| 79 | 
            -
             | 
| 88 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.has_many_relation.title', 
         | 
| 89 | 
            +
                                                              default: 'Relation exists'),
         | 
| 90 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.has_many_relation.detail',
         | 
| 91 | 
            +
                                                               default: "The relation to #{id} already exists.",
         | 
| 92 | 
            +
                                                               id: id))]
         | 
| 80 93 | 
             
                  end
         | 
| 81 94 | 
             
                end
         | 
| 82 95 |  | 
| @@ -84,8 +97,10 @@ module JSONAPI | |
| 84 97 | 
             
                  def errors
         | 
| 85 98 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::FORBIDDEN,
         | 
| 86 99 | 
             
                                        status: :forbidden,
         | 
| 87 | 
            -
                                        title: ' | 
| 88 | 
            -
             | 
| 100 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.to_many_set_replacement_forbidden.title', 
         | 
| 101 | 
            +
                                                              default: 'Complete replacement forbidden'),
         | 
| 102 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.to_many_set_replacement_forbidden.detail',
         | 
| 103 | 
            +
                                                               default: 'Complete replacement forbidden for this relationship'))]
         | 
| 89 104 | 
             
                  end
         | 
| 90 105 | 
             
                end
         | 
| 91 106 |  | 
| @@ -98,8 +113,11 @@ module JSONAPI | |
| 98 113 | 
             
                  def errors
         | 
| 99 114 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::INVALID_FILTERS_SYNTAX,
         | 
| 100 115 | 
             
                                        status: :bad_request,
         | 
| 101 | 
            -
                                        title: ' | 
| 102 | 
            -
             | 
| 116 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.invalid_filter_syntax.title',
         | 
| 117 | 
            +
                                                              default: 'Invalid filters syntax'),
         | 
| 118 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.invalid_filter_syntax.detail',
         | 
| 119 | 
            +
                                                               default: "#{filters} is not a valid syntax for filtering.",
         | 
| 120 | 
            +
                                                               filters: filters))]
         | 
| 103 121 | 
             
                  end
         | 
| 104 122 | 
             
                end
         | 
| 105 123 |  | 
| @@ -112,8 +130,10 @@ module JSONAPI | |
| 112 130 | 
             
                  def errors
         | 
| 113 131 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::FILTER_NOT_ALLOWED,
         | 
| 114 132 | 
             
                                        status: :bad_request,
         | 
| 115 | 
            -
                                        title: ' | 
| 116 | 
            -
             | 
| 133 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.filter_not_allowed.title',
         | 
| 134 | 
            +
                                                              default: 'Filter not allowed'),
         | 
| 135 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.filter_not_allowed.detail',
         | 
| 136 | 
            +
                                                               default: "#{filter} is not allowed.", filter: filter))]
         | 
| 117 137 | 
             
                  end
         | 
| 118 138 | 
             
                end
         | 
| 119 139 |  | 
| @@ -127,8 +147,11 @@ module JSONAPI | |
| 127 147 | 
             
                  def errors
         | 
| 128 148 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::INVALID_FILTER_VALUE,
         | 
| 129 149 | 
             
                                        status: :bad_request,
         | 
| 130 | 
            -
                                        title: ' | 
| 131 | 
            -
             | 
| 150 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.invalid_filter_value.title',
         | 
| 151 | 
            +
                                                              default: 'Invalid filter value'),
         | 
| 152 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.invalid_filter_value.detail',
         | 
| 153 | 
            +
                                                               default: "#{value} is not a valid value for #{filter}.",
         | 
| 154 | 
            +
                                                               value: value, filter: filter))]
         | 
| 132 155 | 
             
                  end
         | 
| 133 156 | 
             
                end
         | 
| 134 157 |  | 
| @@ -142,8 +165,11 @@ module JSONAPI | |
| 142 165 | 
             
                  def errors
         | 
| 143 166 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::INVALID_FIELD_VALUE,
         | 
| 144 167 | 
             
                                        status: :bad_request,
         | 
| 145 | 
            -
                                        title: ' | 
| 146 | 
            -
             | 
| 168 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.invalid_field_value.title',
         | 
| 169 | 
            +
                                                              default: 'Invalid field value'),
         | 
| 170 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.invalid_field_value.detail',
         | 
| 171 | 
            +
                                                               default: "#{value} is not a valid value for #{field}.",
         | 
| 172 | 
            +
                                                               value: value, field: field))]
         | 
| 147 173 | 
             
                  end
         | 
| 148 174 | 
             
                end
         | 
| 149 175 |  | 
| @@ -151,8 +177,10 @@ module JSONAPI | |
| 151 177 | 
             
                  def errors
         | 
| 152 178 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::INVALID_FIELD_FORMAT,
         | 
| 153 179 | 
             
                                        status: :bad_request,
         | 
| 154 | 
            -
                                        title: ' | 
| 155 | 
            -
             | 
| 180 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.invalid_field_format.title',
         | 
| 181 | 
            +
                                                              default: 'Invalid field format'),
         | 
| 182 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.invalid_field_format.detail',
         | 
| 183 | 
            +
                                                               default: 'Fields must specify a type.'))]
         | 
| 156 184 | 
             
                  end
         | 
| 157 185 | 
             
                end
         | 
| 158 186 |  | 
| @@ -160,8 +188,10 @@ module JSONAPI | |
| 160 188 | 
             
                  def errors
         | 
| 161 189 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::INVALID_LINKS_OBJECT,
         | 
| 162 190 | 
             
                                        status: :bad_request,
         | 
| 163 | 
            -
                                        title: ' | 
| 164 | 
            -
             | 
| 191 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.invalid_links_object.title',
         | 
| 192 | 
            +
                                                              default: 'Invalid Links Object'),
         | 
| 193 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.invalid_links_object.detail',
         | 
| 194 | 
            +
                                                               default: 'Data is not a valid Links Object.'))]
         | 
| 165 195 | 
             
                  end
         | 
| 166 196 | 
             
                end
         | 
| 167 197 |  | 
| @@ -174,8 +204,10 @@ module JSONAPI | |
| 174 204 | 
             
                  def errors
         | 
| 175 205 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::TYPE_MISMATCH,
         | 
| 176 206 | 
             
                                        status: :bad_request,
         | 
| 177 | 
            -
                                        title: ' | 
| 178 | 
            -
             | 
| 207 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.type_mismatch.title',
         | 
| 208 | 
            +
                                                              default: 'Type Mismatch'),
         | 
| 209 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.type_mismatch.detail',
         | 
| 210 | 
            +
                                                               default: "#{type} is not a valid type for this operation.", type: type))]
         | 
| 179 211 | 
             
                  end
         | 
| 180 212 | 
             
                end
         | 
| 181 213 |  | 
| @@ -189,8 +221,11 @@ module JSONAPI | |
| 189 221 | 
             
                  def errors
         | 
| 190 222 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::INVALID_FIELD,
         | 
| 191 223 | 
             
                                        status: :bad_request,
         | 
| 192 | 
            -
                                        title: ' | 
| 193 | 
            -
             | 
| 224 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.invalid_field.title',
         | 
| 225 | 
            +
                                                              default: 'Invalid field'),
         | 
| 226 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.invalid_field.detail',
         | 
| 227 | 
            +
                                                               default: "#{field} is not a valid field for #{type}.",
         | 
| 228 | 
            +
                                                               field: field, type: type))]
         | 
| 194 229 | 
             
                  end
         | 
| 195 230 | 
             
                end
         | 
| 196 231 |  | 
| @@ -204,8 +239,11 @@ module JSONAPI | |
| 204 239 | 
             
                  def errors
         | 
| 205 240 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::INVALID_INCLUDE,
         | 
| 206 241 | 
             
                                        status: :bad_request,
         | 
| 207 | 
            -
                                        title: ' | 
| 208 | 
            -
             | 
| 242 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.invalid_include.title',
         | 
| 243 | 
            +
                                                              default: 'Invalid field'),
         | 
| 244 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.invalid_include.detail',
         | 
| 245 | 
            +
                                                               default: "#{relationship} is not a valid relationship of #{resource}",
         | 
| 246 | 
            +
                                                               relationship: relationship, resource: resource))]
         | 
| 209 247 | 
             
                  end
         | 
| 210 248 | 
             
                end
         | 
| 211 249 |  | 
| @@ -219,8 +257,11 @@ module JSONAPI | |
| 219 257 | 
             
                  def errors
         | 
| 220 258 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::INVALID_SORT_CRITERIA,
         | 
| 221 259 | 
             
                                        status: :bad_request,
         | 
| 222 | 
            -
                                        title: ' | 
| 223 | 
            -
             | 
| 260 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.invalid_sort_criteria.title',
         | 
| 261 | 
            +
                                                              default: 'Invalid sort criteria'),
         | 
| 262 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.invalid_sort_criteria.detail',
         | 
| 263 | 
            +
                                                               default: "#{sort_criteria} is not a valid sort criteria for #{resource}",
         | 
| 264 | 
            +
                                                               sort_criteria: sort_criteria, resource: resource))]
         | 
| 224 265 | 
             
                  end
         | 
| 225 266 | 
             
                end
         | 
| 226 267 |  | 
| @@ -234,8 +275,11 @@ module JSONAPI | |
| 234 275 | 
             
                    params.collect do |param|
         | 
| 235 276 | 
             
                      JSONAPI::Error.new(code: JSONAPI::PARAM_NOT_ALLOWED,
         | 
| 236 277 | 
             
                                         status: :bad_request,
         | 
| 237 | 
            -
                                         title: ' | 
| 238 | 
            -
             | 
| 278 | 
            +
                                         title: I18n.translate('jsonapi-resources.exceptions.parameters_not_allowed.title',
         | 
| 279 | 
            +
                                                               default: 'Param not allowed'),
         | 
| 280 | 
            +
                                         detail: I18n.translate('jsonapi-resources.exceptions.parameters_not_allowed.detail',
         | 
| 281 | 
            +
                                                                default: "#{param} is not allowed.", param: param))
         | 
| 282 | 
            +
             | 
| 239 283 | 
             
                    end
         | 
| 240 284 | 
             
                  end
         | 
| 241 285 | 
             
                end
         | 
| @@ -249,8 +293,10 @@ module JSONAPI | |
| 249 293 | 
             
                  def errors
         | 
| 250 294 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::PARAM_MISSING,
         | 
| 251 295 | 
             
                                        status: :bad_request,
         | 
| 252 | 
            -
                                        title: ' | 
| 253 | 
            -
             | 
| 296 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.parameter_missing.title',
         | 
| 297 | 
            +
                                                              default: 'Missing Parameter'),
         | 
| 298 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.parameter_missing.detail',
         | 
| 299 | 
            +
                                                               default: "The required parameter, #{param}, is missing.", param: param))]
         | 
| 254 300 | 
             
                  end
         | 
| 255 301 | 
             
                end
         | 
| 256 302 |  | 
| @@ -258,8 +304,10 @@ module JSONAPI | |
| 258 304 | 
             
                  def errors
         | 
| 259 305 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::COUNT_MISMATCH,
         | 
| 260 306 | 
             
                                        status: :bad_request,
         | 
| 261 | 
            -
                                        title: ' | 
| 262 | 
            -
             | 
| 307 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.count_mismatch.title',
         | 
| 308 | 
            +
                                                              default: 'Count to key mismatch'),
         | 
| 309 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.count_mismatch.detail',
         | 
| 310 | 
            +
                                                               default: 'The resource collection does not contain the same number of objects as the number of keys.'))]
         | 
| 263 311 | 
             
                  end
         | 
| 264 312 | 
             
                end
         | 
| 265 313 |  | 
| @@ -272,8 +320,11 @@ module JSONAPI | |
| 272 320 | 
             
                  def errors
         | 
| 273 321 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::KEY_NOT_INCLUDED_IN_URL,
         | 
| 274 322 | 
             
                                        status: :bad_request,
         | 
| 275 | 
            -
                                        title: ' | 
| 276 | 
            -
             | 
| 323 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.key_not_included_in_url.title',
         | 
| 324 | 
            +
                                                              default: 'Key is not included in URL'),
         | 
| 325 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.key_not_included_in_url.detail',
         | 
| 326 | 
            +
                                                               default: "The URL does not support the key #{key}",
         | 
| 327 | 
            +
                                                               key: key))]
         | 
| 277 328 | 
             
                  end
         | 
| 278 329 | 
             
                end
         | 
| 279 330 |  | 
| @@ -281,8 +332,10 @@ module JSONAPI | |
| 281 332 | 
             
                  def errors
         | 
| 282 333 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::KEY_ORDER_MISMATCH,
         | 
| 283 334 | 
             
                                        status: :bad_request,
         | 
| 284 | 
            -
                                        title: ' | 
| 285 | 
            -
             | 
| 335 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.missing_key.title',
         | 
| 336 | 
            +
                                                              default: 'A key is required'),
         | 
| 337 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.missing_key.detail',
         | 
| 338 | 
            +
                                                               default: 'The resource object does not contain a key.'))]
         | 
| 286 339 | 
             
                  end
         | 
| 287 340 | 
             
                end
         | 
| 288 341 |  | 
| @@ -295,16 +348,18 @@ module JSONAPI | |
| 295 348 | 
             
                  def errors
         | 
| 296 349 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::LOCKED,
         | 
| 297 350 | 
             
                                        status: :locked,
         | 
| 298 | 
            -
                                        title: ' | 
| 351 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.record_locked.title',
         | 
| 352 | 
            +
                                                              default: 'Locked resource'),
         | 
| 299 353 | 
             
                                        detail: "#{message}")]
         | 
| 300 354 | 
             
                  end
         | 
| 301 355 | 
             
                end
         | 
| 302 356 |  | 
| 303 357 | 
             
                class ValidationErrors < Error
         | 
| 304 | 
            -
                  attr_reader :error_messages, :resource_relationships
         | 
| 358 | 
            +
                  attr_reader :error_messages, :error_metadata, :resource_relationships
         | 
| 305 359 |  | 
| 306 360 | 
             
                  def initialize(resource)
         | 
| 307 361 | 
             
                    @error_messages = resource.model_error_messages
         | 
| 362 | 
            +
                    @error_metadata = resource.validation_error_metadata
         | 
| 308 363 | 
             
                    @resource_relationships = resource.class._relationships.keys
         | 
| 309 364 | 
             
                    @key_formatter = JSONAPI.configuration.key_formatter
         | 
| 310 365 | 
             
                  end
         | 
| @@ -326,7 +381,13 @@ module JSONAPI | |
| 326 381 | 
             
                                       status: :unprocessable_entity,
         | 
| 327 382 | 
             
                                       title: message,
         | 
| 328 383 | 
             
                                       detail: "#{format_key(attr_key)} - #{message}",
         | 
| 329 | 
            -
                                       source: { pointer: pointer(attr_key) } | 
| 384 | 
            +
                                       source: { pointer: pointer(attr_key) },
         | 
| 385 | 
            +
                                       meta: metadata_for(attr_key, message))
         | 
| 386 | 
            +
                  end
         | 
| 387 | 
            +
             | 
| 388 | 
            +
                  def metadata_for(attr_key, message)
         | 
| 389 | 
            +
                    return if error_metadata.nil?
         | 
| 390 | 
            +
                    error_metadata[attr_key] ?  error_metadata[attr_key][message] : nil
         | 
| 330 391 | 
             
                  end
         | 
| 331 392 |  | 
| 332 393 | 
             
                  def pointer(attr_or_relationship_name)
         | 
| @@ -343,8 +404,10 @@ module JSONAPI | |
| 343 404 | 
             
                  def errors
         | 
| 344 405 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::SAVE_FAILED,
         | 
| 345 406 | 
             
                                        status: :unprocessable_entity,
         | 
| 346 | 
            -
                                        title: ' | 
| 347 | 
            -
             | 
| 407 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.save_failed.title',
         | 
| 408 | 
            +
                                                              default: 'Save failed or was cancelled'),
         | 
| 409 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.save_failed.detail',
         | 
| 410 | 
            +
                                                               default: 'Save failed or was cancelled'))]
         | 
| 348 411 | 
             
                  end
         | 
| 349 412 | 
             
                end
         | 
| 350 413 |  | 
| @@ -352,8 +415,10 @@ module JSONAPI | |
| 352 415 | 
             
                  def errors
         | 
| 353 416 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::INVALID_PAGE_OBJECT,
         | 
| 354 417 | 
             
                                        status: :bad_request,
         | 
| 355 | 
            -
                                        title: ' | 
| 356 | 
            -
             | 
| 418 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.invalid_page_object.title',
         | 
| 419 | 
            +
                                                              default: 'Invalid Page Object'),
         | 
| 420 | 
            +
                                        detail: I18n.translate('jsonapi-resources.exceptions.invalid_page_object.detail',
         | 
| 421 | 
            +
                                                               default: 'Invalid Page Object.'))]
         | 
| 357 422 | 
             
                  end
         | 
| 358 423 | 
             
                end
         | 
| 359 424 |  | 
| @@ -367,8 +432,11 @@ module JSONAPI | |
| 367 432 | 
             
                    params.collect do |param|
         | 
| 368 433 | 
             
                      JSONAPI::Error.new(code: JSONAPI::PARAM_NOT_ALLOWED,
         | 
| 369 434 | 
             
                                         status: :bad_request,
         | 
| 370 | 
            -
                                         title: ' | 
| 371 | 
            -
             | 
| 435 | 
            +
                                         title: I18n.translate('jsonapi-resources.exceptions.page_parameters_not_allowed.title',
         | 
| 436 | 
            +
                                                               default: 'Page parameter not allowed'),
         | 
| 437 | 
            +
                                         detail: I18n.translate('jsonapi-resources.exceptions.page_parameters_not_allowed.detail',
         | 
| 438 | 
            +
                                                                default: "#{param} is not an allowed page parameter.",
         | 
| 439 | 
            +
                                                                param: param))
         | 
| 372 440 | 
             
                    end
         | 
| 373 441 | 
             
                  end
         | 
| 374 442 | 
             
                end
         | 
| @@ -378,13 +446,16 @@ module JSONAPI | |
| 378 446 | 
             
                  def initialize(page, value, msg = nil)
         | 
| 379 447 | 
             
                    @page = page
         | 
| 380 448 | 
             
                    @value = value
         | 
| 381 | 
            -
                    @msg = msg ||  | 
| 449 | 
            +
                    @msg = msg || I18n.translate('jsonapi-resources.exceptions.invalid_page_value.detail',
         | 
| 450 | 
            +
                                                 default: "#{value} is not a valid value for #{page} page parameter.",
         | 
| 451 | 
            +
                                                 value: value, page: page)
         | 
| 382 452 | 
             
                  end
         | 
| 383 453 |  | 
| 384 454 | 
             
                  def errors
         | 
| 385 455 | 
             
                    [JSONAPI::Error.new(code: JSONAPI::INVALID_PAGE_VALUE,
         | 
| 386 456 | 
             
                                        status: :bad_request,
         | 
| 387 | 
            -
                                        title: ' | 
| 457 | 
            +
                                        title: I18n.translate('jsonapi-resources.exceptions.invalid_page_value.title',
         | 
| 458 | 
            +
                                                              default: 'Invalid page value'),
         | 
| 388 459 | 
             
                                        detail: @msg)]
         | 
| 389 460 | 
             
                  end
         | 
| 390 461 | 
             
                end
         |