Skip to content

Errors

import 'package:gisila/gisila.dart';

Every framework error and every HttpException you throw is rendered as:

{"error": {"status": 404, "message": "User \"42\" not found", "code": "not_found", "details": {}}}

code and details are present only when set.

The error mapper sits around routing and your app.use() middleware. For an exception thrown by a controller, service or middleware:

Exception Response
HttpException (including every subclass below) Its statusCode and toJson(). Not logged.
FormValidationException 400, code validation_failed, the field errors as details.
Anything AppConfig.dbErrorMapper maps The HttpException it returns.
Anything else, with AppConfig.errorHandler set Whatever the handler returns (the framework doesn’t log it).
Anything else Logged at error with the stack trace, then 500 with code internal_error and the message Internal server error. The exception text is never sent to the client.

Framework-generated errors use these codes: bad_request (binding and parsing), validation_failed, unauthorized, invalid_token, token_expired, invalid_api_key, forbidden, not_found (no route), payload_too_large, unsupported_media_type, too_many_requests, gateway_timeout, internal_error.

Base class for exceptions that map to an HTTP status.

class HttpException implements Exception {
HttpException(int statusCode, String message, {String? code, Map<String, Object?>? details});
final int statusCode;
final String message;
final String? code;
final Map<String, Object?>? details;
Map<String, Object?> toJson();
}

When to use: throw it (or a subclass) from controllers and services whenever the client should see a specific status. Use the named subclasses for common statuses and HttpException directly for others (422, 503). Subclass it and override toJson() for a different body shape.

Field Description
statusCode HTTP status.
message Human-readable message, sent to the client.
code Optional machine-readable code (user_not_found). Clients should branch on this, not on message.
details Optional structured data under error.details.
toJson() The envelope above.

This is gisila’s own class, not dart:io’s HttpException. If a file imports both, hide one: import 'dart:io' hide HttpException;.

throw HttpException(503, 'Payment provider unavailable', code: 'provider_down');
throw HttpException(422, 'Order cannot be shipped', code: 'not_shippable',
details: {'missing': ['address']});
Class Constructor Status Default message Code
BadRequest BadRequest(String message, {String? code, Map<String, Object?>? details}) 400 (required) code or none
Unauthorized Unauthorized([String message]) 401 Authentication required unauthorized
Forbidden Forbidden([String message]) 403 Access denied forbidden
NotFound NotFound([String message]) 404 Resource not found not_found
Conflict Conflict(String message, {String? code, Map<String, Object?>? details}) 409 (required) code or conflict
PayloadTooLarge PayloadTooLarge([String message]) 413 Request body too large payload_too_large
TooManyRequests TooManyRequests([String message]) 429 Too many requests too_many_requests
InternalServerError InternalServerError([String message]) 500 Internal server error internal_error

When to use:

  • BadRequest: input that is well-formed but wrong, when a form can’t express the rule.
  • Unauthorized: credentials missing or invalid (inside an authenticator or a login service).
  • Forbidden: the caller is known but not allowed (ownership checks the guards can’t express).
  • NotFound: a resource doesn’t exist, or the caller must not learn that it exists.
  • Conflict: duplicates and state conflicts (email taken, version mismatch).
  • TooManyRequests, PayloadTooLarge: custom limits of your own.
  • InternalServerError: rarely; prefer letting unexpected errors propagate so they are logged.
class UserService extends Service {
Future<User> getById(String id) async {
final user = await _repo.find(id);
if (user == null) throw NotFound('User "$id" not found');
if (user.ownerId != ctx.principal!.id) throw NotFound('User "$id" not found');
return user;
}
}

An HttpException with full control of the response body.

class EndpointException extends HttpException {
EndpointException({
required int statusCode,
Map<String, Object?>? body,
String? message,
String? code,
});
}

toJson() returns body as-is when given, otherwise the standard envelope. message defaults to body['detail'] or body['message'], then 'Endpoint error'; the body is also stored as details.

When to use: when a client or spec expects a specific error shape (for example a third-party webhook contract). For your own API, keep the standard envelope.

throw EndpointException(
statusCode: 422,
body: {'detail': 'Order rejected by payment processor', 'retryable': false},
);
class BadRequestException extends HttpException {
BadRequestException(String message); // status 400, code 'bad_request'
}

Thrown by the body parsers, coerce and generated binding code for missing or malformed input. You can throw it too, but BadRequest lets you choose the code.

See also: Handle errors guide, ErrorHandler, DbErrorMapper.