op_rest_api_client 0.14.0
op_rest_api_client: ^0.14.0 copied to clipboard
A Dart http client to send requests to REST API.
op_rest_api_client #
A lightweight, extensible Dart package for interacting with RESTful APIs.
Provides structured API requests, robust error handling, authentication support, and identity-based token management, while leveraging op_result for structured responses.
• 🔐 **Authentication support**: Integrates with `Identity` for token-based authentication, including automatic token refresh.
• ❌ **Robust error handling**: Utilizes `OpResult<T>` for structured success and failure responses.
• 🎯 **Type-safe API endpoints**: Encourages defining API endpoints using enums or constants for better maintainability.
• 🚧 **Automatic token refresh**: Automatically retries requests with a refreshed token upon receiving a `401 Unauthorized`.
• ✅ **Custom response validation**: Allows fine-grained validation via `customValidator`.
📦 Installation #
Add op_rest_api_client to your pubspec.yaml:
dependencies:
op_rest_api_client: ^0.14.0
Or install via CLI:
dart pub add op_rest_api_client
🛠️ Usage #
1️⃣ Define API Endpoints as an Enum #
enum MyApiEndpoints {
getUser,
updateUser,
deleteUser,
}
OpRestApiEndpoint getEndpoint(MyApiEndpoints endpoint) {
switch (endpoint) {
case MyApiEndpoints.getUser:
return OpRestApiEndpoint('/user', OpRestApiClientMethods.get);
case MyApiEndpoints.updateUser:
return OpRestApiEndpoint('/user/update', OpRestApiClientMethods.put);
case MyApiEndpoints.deleteUser:
return OpRestApiEndpoint('/user/delete', OpRestApiClientMethods.delete);
}
}
2️⃣ Initialize the API Client #
import 'package:op_rest_api_client/op_rest_api_client.dart';
import 'package:http/http.dart' as http;
final apiClient = OpRestApiClient<MyApiEndpoints>(
baseUrl: "https://api.example.com",
client: http.Client(),
);
3️⃣ Send API Requests #
🔹 GET Request
final result = await apiClient.send(
endpoint: getEndpoint(MyApiEndpoints.getUser),
identity: userIdentity, // Optional authentication
);
if (result.isSuccess) {
print("User data: \${result.data.body}");
} else {
print("Error: \${result.error.getErrorMessage()}");
}
🔹 POST Request (With Body)
final newUser = {
'name': 'John Doe',
'email': '[email protected]',
};
final result = await apiClient.send(
endpoint: getEndpoint(MyApiEndpoints.updateUser),
identity: userIdentity,
body: newUser,
);
if (result.isSuccess) {
print("User updated successfully!");
} else {
print("Error: \${result.error.getErrorMessage()}");
}
🔹 DELETE Request
final result = await apiClient.send(
endpoint: getEndpoint(MyApiEndpoints.deleteUser),
identity: userIdentity,
);
if (result.isSuccess) {
print("User deleted!");
} else {
print("Error: \${result.error.getErrorMessage()}");
}
📌 Authentication & Token Refresh #
The client supports authentication via OpRestApiIdentity, handling token refresh automatically.
class MyApiIdentity extends OpRestApiIdentity<String> {
final String accessToken;
final String refreshToken;
MyApiIdentity({
required this.accessToken,
required this.refreshToken,
});
@override
String get identityData => accessToken;
@override
bool isValid() => accessToken.isNotEmpty;
@override
bool isExpired() {
// Example logic for expiration check (adjust as needed)
return false;
}
@override
String authorizationHeader() => "Bearer $accessToken";
@override
Future<OpRestApiIdentity<String>?> refresh() async {
// Refresh token logic (example)
return MyApiIdentity(
accessToken: "newAccessToken",
refreshToken: "newRefreshToken",
);
}
}
final userIdentity = MyApiIdentity(
accessToken: "abc123",
refreshToken: "refresh123",
);
If a request fails due to an expired token (401):
- The client automatically attempts token refresh once.
- If the refresh succeeds, the original request is retried automatically using the new token.
- If the refresh fails, an unauthenticated error (
OpError(AuthError.unauthenticated)) is returned.
❌ Error Handling #
All API responses are wrapped in OpResult<http.Response>, which indicates success or failure.
📌 Error Handling #
Usage of customValidator:
OpResult<http.Response, MyCustomError>? myValidator(http.Response response) {
if (response.statusCode == 200 || response.statusCode == 202) {
return OpResult.success(response);
}
if (response.statusCode == 404) {
return OpResult.failure(OpError(type: MyCustomError.notFound));
}
return null; // Fallback to default handling
}
final result = await apiClient.send(
endpoint: getEndpoint(MyApiEndpoints.getUser),
customValidator: myValidator,
);
🛠 Advanced Configuration #
🌐 Custom Headers #
await apiClient.send(
endpoint: getEndPoint(MyApiEndpoints.getUser),
headers: {
"X-Custom-Header": "value",
},
);
📜 Enforcing Response Type #
If an API is expected to return a certain content type (e.g. JSON) but sometimes it doesn't, then the optional responseMapper parameter can be used to enforce that format:
final apiClient = OpRestApiClient<MyApiEndpoints>(
baseUrl: "https://api.example.com",
client: http.Client(),
responseMapper: (response) {
// Example: Ensure response is always JSON
if (!response.headers['content-type']?.contains('application/json') ?? true) {
throw FormatException('Invalid response format');
}
return response;
},
);
🚀 Why Use op_rest_api_client? #
✔ Structured API calls → No need to manage HTTP manually.
✔ Authentication & token refresh → Handles 401 errors automatically.
✔ Strong typing with enums → Prevents invalid API endpoints.
✔ Full OpResult support → Clear success/failure responses.
✔ Custom error handling → Map HTTP codes to app-specific errors.
📌 Versioning & Changelog #
For release history and breaking changes, check the CHANGELOG.
⚖ License #
This project is licensed under the BSD 3-Clause License.