smart_api_errors_parser 1.0.0
smart_api_errors_parser: ^1.0.0 copied to clipboard
Maps inconsistent API error payloads into typed exceptions, form errors, and localized messages.
smart_api_errors_parser #
Normalize inconsistent API failures into typed exceptions, form errors, global errors, and localized messages. Core mapping stays transport-neutral; Dio integration uses a dedicated entrypoint.
Complete runnable Flutter app: example application.
Supported response formats #
Automatic detection supports:
- Nested validation maps used by Laravel and similar APIs.
- Detail-object lists.
- RFC 9457 Problem Details and ASP.NET validation extensions.
- JSON:API error objects.
- Flat field maps used by Django REST Framework.
- GraphQL errors.
- OAuth 2.0 errors.
- Spring Boot legacy validation responses.
- Rails ActiveModel errors.
- Firebase callable-function errors.
- Plain string responses.
import 'package:smart_api_errors_parser/smart_api_errors_parser.dart';
final errors = ApiErrorMapper.map(response.data);
formController.setErrors(errors.fieldErrors);
showSnackbar(errors.userMessage);
// Diagnostics and response metadata:
print(errors.decoderUsed);
print(errors.schema);
print(errors.warnings);
print(errors.httpStatusCode);
print(errors.bodyStatusCode);
print(errors.requestId ?? errors.traceId);
ApiErrorResult.decoded exposes the complete ApiDecodedResponse, including
Problem Details type, instance, and title, request/trace IDs,
Retry-After, the decoder/schema used, and non-fatal parse warnings.
Select a known schema when automatic detection is undesirable:
final errors = ApiErrorMapper.map(
response.data,
schema: ApiErrorSchema.problemDetails,
);
Field paths and localization #
Normalize JSON pointers or nested backend paths before sending errors to a form:
final mapper = ApiErrorMapper(
fieldPathNormalizer: ApiFieldPathNormalizers.compose([
ApiFieldPathNormalizers.jsonPointerToDot,
ApiFieldPathNormalizers.removePrefix('data.attributes.'),
ApiFieldPathNormalizers.indexedPath,
ApiFieldPathNormalizers.snakeToCamel,
ApiFieldPathNormalizers.aliases({'userEmail': 'email'}),
]),
localizer: (error) => translations[error.code] ?? error.message,
);
Transforms are composable and also include camelToSnake, bracket-index
normalization (items[0].name to items.0.name), and lastSegment.
Dio integration #
Import the dedicated Dio entrypoint:
import 'package:smart_api_errors_parser/smart_api_errors_parser_dio.dart';
dio.interceptors.add(
ApiErrorInterceptor(
mapper: ApiErrorMapper(
localizer: (error) => translations[error.code] ?? error.message,
),
),
);
Unwrap requests at repository boundary:
Future<void> register(Map<String, Object?> data) async {
await dio.post<void>('/register', data: data).unwrapApiErrors();
}
Presentation code can then catch typed exceptions directly:
try {
await repository.register(data);
} on ApiValidationException catch (error) {
formController.setErrors(
error.fields,
scrollToFirstError: true,
);
} on ApiException catch (error) {
showSnackbar(error.message);
}
Without unwrapApiErrors(), Dio continues throwing DioException; mapped
ApiException is stored in error.error. Cancelled requests remain
DioException values.
Custom decoder #
Implement ApiErrorDecoder for a reusable backend contract:
final class EnvelopeDecoder implements ApiErrorDecoder {
const EnvelopeDecoder();
@override
bool canDecode(Object? data) => data is Map && data['problem'] is Map;
@override
Iterable<ApiErrorMessage> decode(Object? data) sync* {
final problem = (data as Map)['problem'] as Map;
yield ApiErrorMessage(
field: problem['path']?.toString(),
message: problem['text'].toString(),
);
}
}
const mapper = ApiErrorMapper(decoder: EnvelopeDecoder());
customParser remains available for small one-off integrations. Decoder or
parser failures intentionally propagate so contract bugs remain visible.
Custom exception policy #
Change status groups without rewriting Dio integration:
const classifier = ApiExceptionClassifier(
validationStatusCodes: {400, 409, 422},
conflictStatusCodes: {},
);
dio.interceptors.add(
ApiErrorInterceptor(exceptionFactory: classifier.classify),
);
ApiException is extensible. Supply ApiExceptionFactory when application
needs domain-specific exception subclasses.
Migrating from 0.1.x #
Dio APIs moved out of core entrypoint. Replace:
import 'package:smart_api_errors_parser/smart_api_errors_parser.dart';
with:
import 'package:smart_api_errors_parser/smart_api_errors_parser_dio.dart';
Core-only consumers keep existing import unchanged.
Migrating to 1.0.0 #
Automatic selection now evaluates every built-in decoder by confidence.
Check result.decoderUsed, result.schema, and result.warnings when an API
uses an ambiguous envelope. Existing custom ApiErrorDecoder implementations
remain compatible; implement RichApiErrorDecoder when custom decoders need to
return metadata.