Files
recipe-app/flutter/lib/core/api/api_client.dart
T

141 lines
4.1 KiB
Dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'api_exception.dart';
/// Platform-neutral HTTP client.
/// API base URL is injected at build time via --dart-define=API_BASE_URL.
/// Default is same-origin '/api' to avoid mixed-content on HTTPS sites.
class ApiClient {
final String baseUrl;
final http.Client _client;
ApiClient({http.Client? client})
: baseUrl = const String.fromEnvironment(
'API_BASE_URL',
defaultValue: '/api',
),
_client = client ?? http.Client();
Map<String, String> _headers({String? token}) => {
'Content-Type': 'application/json',
if (token != null) 'Authorization': 'Bearer $token',
};
Future<dynamic> getJson(String path, {String? token}) async {
final response = await _client.get(
Uri.parse('$baseUrl$path'),
headers: _headers(token: token),
);
return _decodeOrNull(_guardResponse(response));
}
Future<dynamic> postJson(
String path, {
Object? body,
String? token,
}) async {
final response = await _client.post(
Uri.parse('$baseUrl$path'),
headers: _headers(token: token),
body: body == null ? null : jsonEncode(body),
);
return _decodeOrNull(_guardResponse(response));
}
Future<dynamic> putJson(
String path, {
Object? body,
String? token,
}) async {
final response = await _client.put(
Uri.parse('$baseUrl$path'),
headers: _headers(token: token),
body: body == null ? null : jsonEncode(body),
);
return _decodeOrNull(_guardResponse(response));
}
Future<dynamic> patchJson(
String path, {
Object? body,
String? token,
}) async {
final response = await _client.patch(
Uri.parse('$baseUrl$path'),
headers: _headers(token: token),
body: body == null ? null : jsonEncode(body),
);
return _decodeOrNull(_guardResponse(response));
}
}
final parsedBody = _decodeOrNull(response);
final serverMessage = _extractMessage(parsedBody);
if (response.statusCode == 401) {
throw ApiException(
type: ApiErrorType.unauthorized,
statusCode: 401,
message: serverMessage ?? 'Unauthorized',
);
}
if (response.statusCode == 403) {
throw ApiException(
type: ApiErrorType.forbidden,
statusCode: 403,
message: serverMessage ?? 'Forbidden',
);
}
if (response.statusCode >= 500) {
throw ApiException(
type: ApiErrorType.server,
statusCode: response.statusCode,
message: serverMessage ?? 'Server error',
);
}
throw ApiException(
type: ApiErrorType.unknown,
statusCode: response.statusCode,
message: serverMessage ?? 'Request failed',
);
}
dynamic _decodeOrNull(http.Response response) {
final body = response.body.trim();
if (body.isEmpty) {
return null;
}
try {
return jsonDecode(body);
} catch (_) {
return body;
}
}
String? _extractMessage(dynamic parsedBody) {
if (parsedBody is Map<String, dynamic>) {
final message = parsedBody['message'];
if (message is String && message.trim().isNotEmpty) {
return message;
}
if (message is List && message.isNotEmpty) {
return message.first.toString();
}
final error = parsedBody['error'];
if (error is String && error.trim().isNotEmpty) {
return error;
}
}
if (parsedBody is String && parsedBody.trim().isNotEmpty) {
return parsedBody;
}
return null;
}
}