-
Notifications
You must be signed in to change notification settings - Fork 3
Description
Problem
The base ServiceError.api_error method returns :bad_request (400) as the default error code:
# lib/servus/support/errors.rb:26
class ServiceError < StandardError
def api_error
{ code: :bad_request, message: message }
end
endThis means whenever services call failure(message), they return a 400 status code. However, these are typically validation or business logic errors, not malformed request errors.
Expected Behavior
- 400 Bad Request: Malformed requests, syntax errors, invalid JSON
- 422 Unprocessable Entity: Semantic validation errors - request is well-formed but business logic rejects it
Examples from ZAR Core's Digital Cash V2:
- "Digital cash must be in 'deposited' status" → should be 422, not 400
- "Only the original depositor can reclaim this digital cash" → should be 422, not 400
- "Escrow amount mismatch" → should be 422, not 400
Current Impact
ZAR Core has to work around this with a mapping layer in ApiErrorHelpers:
# Temporary workaround - maps :bad_request to :unprocessable_entity
status = error_params.last == 'bad_request' ? :unprocessable_entity : error_params.last.to_symThis hides the problem and means every app using Servus has to implement their own workaround.
Proposed Solution
Change the default in ServiceError.api_error:
class ServiceError < StandardError
def api_error
{ code: :unprocessable_entity, message: message }
end
endBadRequestError would remain unchanged (still returns 400) for actual bad request scenarios.
Migration Impact
This is a breaking change - any services currently relying on failure() returning 400 will start returning 422. However:
- The new behavior is more semantically correct
- It matches Rails/HTTP conventions better
- ZAR Core already expects 422 for these errors
Alternative
If backward compatibility is critical, we could:
- Add a new
ValidationErrorthat returns 422 - Keep
ServiceErrorat 400 - Update documentation to recommend using specific error types instead of
failure()
But I think fixing the default is better since Servus is purpose-built for ZAR.
Related: ZAR Core PR #814 (Digital Cash V2) exposed this issue when adopting run_service pattern.