op_rest_api_client 0.15.0
op_rest_api_client: ^0.15.0 copied to clipboard
A lightweight and flexible Dart HTTP client for RESTful APIs with structured error handling via op_result and support for identity-based authentication and token refresh.
example/op_rest_api_client_example.dart
library;
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:op_rest_api_client/op_rest_api_client.dart';
// Example function to be used in tests
Future<void> runExample() async {
main();
}
// Define API endpoints
enum ApiEndpoints { getPosts, addPost, refreshToken }
OpRestApiEndpoint getApiEndpoint(ApiEndpoints endpoint) {
switch (endpoint) {
case ApiEndpoints.getPosts:
return OpRestApiEndpoint('/posts', OpRestApiClientMethods.get);
case ApiEndpoints.addPost:
return OpRestApiEndpoint('/posts', OpRestApiClientMethods.post);
case ApiEndpoints.refreshToken:
return OpRestApiEndpoint('/auth/refresh', OpRestApiClientMethods.post);
}
}
void main() async {
// Initialize the API client
final apiClient = OpRestApiClient(
baseUrl: 'https://jsonplaceholder.typicode.com',
client: http.Client(),
);
// Example 1: Perform a GET request
print('Fetching posts...');
OpRestApiEndpoint endpoint = getApiEndpoint(ApiEndpoints.getPosts);
final getResponse = await apiClient.send(
endpoint: endpoint,
identity: null, // No authentication required for this endpoint
);
if (getResponse.isSuccess) {
print('Posts fetched successfully:');
print(jsonDecode(getResponse.data.body));
} else {
print('Failed to fetch posts: ${getResponse.error.type}');
}
// Example 2: Perform a POST request
print('\nCreating a new post...');
final postResponse = await apiClient.send(
endpoint: getApiEndpoint(ApiEndpoints.addPost),
identity: null,
body: {'title': 'foo', 'body': 'bar', 'userId': 1},
);
if (postResponse.isSuccess) {
print('Post created successfully:');
print(jsonDecode(postResponse.data.body));
} else {
print('Failed to create post: ${postResponse.error.type}');
}
}
/// Implementation of the abstract class Identity with proper refresh handling.
class AuthIdentity implements OpRestApiIdentity<String> {
final String _accessToken;
final String _refreshToken;
final DateTime _expiry;
final OpRestApiClient _apiClient;
/// Constructor to initialize the token, refresh token, and expiry date.
AuthIdentity({
required String identityData,
required DateTime expiry,
required String refreshToken,
required OpRestApiClient apiClient,
}) : _accessToken = identityData,
_expiry = expiry,
_refreshToken = refreshToken,
_apiClient = apiClient;
/// Get the raw token.
@override
String get identityData => _accessToken;
/// Check if the token is valid.
@override
bool isValid() => _accessToken.isNotEmpty && !isExpired();
/// Check if the token is expired.
@override
bool isExpired() => DateTime.now().isAfter(_expiry);
@override
Map<String, String>? headers() => {'Authorization': 'Bearer $_accessToken'};
/// Attempt to refresh the identity.
@override
Future<OpRestApiIdentity<String>?> refresh() async {
print("Attempting to refresh token...");
try {
final response = await _apiClient.send(
endpoint: getApiEndpoint(ApiEndpoints.refreshToken),
identity: null, // No authentication needed for refresh request
body: {'refreshToken': _refreshToken},
);
if (response.isSuccess) {
final data = jsonDecode(response.data.body);
final newToken = data['accessToken'];
final newExpiry = DateTime.now().add(
Duration(hours: 1),
); // Assume 1-hour expiry
print("Token refreshed successfully: $newToken");
return AuthIdentity(
identityData: newToken,
expiry: newExpiry,
refreshToken: _refreshToken, // Keep the same refresh token
apiClient: _apiClient,
);
} else {
print("Token refresh failed: ${response.error.type}");
return null; // Refresh failed, return null to indicate login required
}
} catch (e) {
print("Error during token refresh: $e");
return null;
}
}
}