Skip to main content

smart_api_errors_parser 1.0.0 copy "smart_api_errors_parser: ^1.0.0" to clipboard
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.

0
likes
160
points
48
downloads

Documentation

API reference

Publisher

verified publisherpinz.dev

Weekly Downloads

Maps inconsistent API error payloads into typed exceptions, form errors, and localized messages.

Repository (GitHub)
View/report issues

Topics

#api #dio #error-handling #localization #validation

License

MIT (license)

Dependencies

dio

More

Packages that depend on smart_api_errors_parser