platform/lib/base_angel_client.dart

329 lines
9.1 KiB
Dart
Raw Normal View History

2016-12-09 00:24:07 +00:00
import 'dart:async';
import 'dart:convert';
import 'package:angel_framework/src/http/angel_http_exception.dart';
import 'package:collection/collection.dart';
import 'package:http/src/base_client.dart' as http;
import 'package:http/src/base_request.dart' as http;
import 'package:http/src/request.dart' as http;
import 'package:http/src/response.dart' as http;
import 'package:http/src/streamed_response.dart' as http;
import 'angel_client.dart';
import 'auth_types.dart' as auth_types;
2016-12-10 14:45:22 +00:00
final RegExp straySlashes = new RegExp(r"(^/)|(/+$)");
2016-12-09 00:24:07 +00:00
const Map<String, String> _readHeaders = const {'Accept': 'application/json'};
2016-12-13 16:34:22 +00:00
const Map<String, String> _writeHeaders = const {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
2016-12-09 00:24:07 +00:00
_buildQuery(Map params) {
if (params == null || params.isEmpty) return "";
List<String> query = [];
params.forEach((k, v) {
2016-12-10 14:45:22 +00:00
query.add('$k=${Uri.encodeQueryComponent(v.toString())}');
2016-12-09 00:24:07 +00:00
});
return '?' + query.join('&');
}
AngelHttpException failure(http.Response response, {error, StackTrace stack}) {
try {
final json = JSON.decode(response.body);
if (json is Map && json['isError'] == true) {
return new AngelHttpException.fromMap(json);
} else {
return new AngelHttpException(error,
message: 'Unhandled exception while connecting to Angel backend.',
statusCode: response.statusCode,
stackTrace: stack);
}
} catch (e, st) {
return new AngelHttpException(error ?? e,
message: 'Unhandled exception while connecting to Angel backend.',
statusCode: response.statusCode,
stackTrace: stack ?? st);
}
}
abstract class BaseAngelClient extends Angel {
@override
String authToken;
final http.BaseClient client;
BaseAngelClient(this.client, String basePath) : super(basePath);
@override
Future<AngelAuthResult> authenticate(
{String type: auth_types.LOCAL,
credentials,
String authEndpoint: '/auth',
String reviveEndpoint: '/auth/token'}) async {
if (type == null) {
final url = '$basePath$reviveEndpoint';
2016-12-13 16:34:22 +00:00
final response = await client.post(url, headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer ${credentials['token']}'
});
2016-12-09 00:24:07 +00:00
try {
if (response.statusCode != 200) {
throw failure(response);
}
final json = JSON.decode(response.body);
if (json is! Map ||
!json.containsKey('data') ||
!json.containsKey('token')) {
2017-01-20 23:57:42 +00:00
throw new AngelHttpException.notAuthenticated(
2016-12-09 00:24:07 +00:00
message:
"Auth endpoint '$url' did not return a proper response.");
}
return new AngelAuthResult.fromMap(json);
} catch (e, st) {
throw failure(response, error: e, stack: st);
}
} else {
final url = '$basePath$authEndpoint/$type';
http.Response response;
if (credentials != null) {
response = await client.post(url,
body: JSON.encode(credentials), headers: _writeHeaders);
} else {
response = await client.post(url, headers: _writeHeaders);
}
try {
if (response.statusCode != 200) {
throw failure(response);
}
final json = JSON.decode(response.body);
if (json is! Map ||
!json.containsKey('data') ||
!json.containsKey('token')) {
2017-01-25 23:25:31 +00:00
throw new AngelHttpException.notAuthenticated(
2016-12-09 00:24:07 +00:00
message:
"Auth endpoint '$url' did not return a proper response.");
}
return new AngelAuthResult.fromMap(json);
} catch (e, st) {
throw failure(response, error: e, stack: st);
}
}
}
2016-12-10 14:50:05 +00:00
Future close() async {
client.close();
}
2017-01-25 23:25:31 +00:00
/// Sends a non-streaming [Request] and returns a non-streaming [Response].
Future<http.Response> sendUnstreamed(
String method, url, Map<String, String> headers,
[body, Encoding encoding]) async {
if (url is String) url = Uri.parse(url);
var request = new http.Request(method, url);
if (headers != null) request.headers.addAll(headers);
if (authToken?.isNotEmpty == true)
request.headers['Authorization'] = 'Bearer $authToken';
if (encoding != null) request.encoding = encoding;
if (body != null) {
if (body is String) {
request.body = body;
} else if (body is List) {
request.bodyBytes = DelegatingList.typed(body);
} else if (body is Map) {
request.bodyFields = DelegatingMap.typed(body);
} else {
throw new ArgumentError('Invalid request body "$body".');
}
}
return http.Response.fromStream(await client.send(request));
}
2016-12-09 00:24:07 +00:00
@override
2016-12-13 16:35:35 +00:00
Service service<T>(String path, {Type type, AngelDeserializer deserializer}) {
2016-12-13 16:34:22 +00:00
String uri = path.toString().replaceAll(straySlashes, "");
2016-12-13 16:35:35 +00:00
return new BaseAngelService(client, this, '$basePath/$uri',
deserializer: deserializer);
2016-12-09 00:24:07 +00:00
}
2016-12-10 17:15:54 +00:00
String _join(url) {
final head = basePath.replaceAll(new RegExp(r'/+$'), '');
2016-12-10 17:28:24 +00:00
final tail = url.replaceAll(straySlashes, '');
2016-12-10 17:15:54 +00:00
return '$head/$tail';
}
@override
Future<http.Response> delete(String url,
{Map<String, String> headers}) async {
2017-01-25 23:25:31 +00:00
return sendUnstreamed('DELETE', _join(url), headers);
2016-12-10 17:15:54 +00:00
}
@override
Future<http.Response> get(String url, {Map<String, String> headers}) async {
2017-01-25 23:25:31 +00:00
return sendUnstreamed('GET', _join(url), headers);
2016-12-10 17:15:54 +00:00
}
@override
Future<http.Response> head(String url, {Map<String, String> headers}) async {
2017-01-25 23:25:31 +00:00
return sendUnstreamed('HEAD', _join(url), headers);
2016-12-10 17:15:54 +00:00
}
@override
Future<http.Response> patch(String url,
{body, Map<String, String> headers}) async {
2017-01-25 23:25:31 +00:00
return sendUnstreamed('PATCH', _join(url), headers, body);
2016-12-10 17:15:54 +00:00
}
@override
Future<http.Response> post(String url,
{body, Map<String, String> headers}) async {
2017-01-25 23:25:31 +00:00
return sendUnstreamed('POST', _join(url), headers, body);
2016-12-10 17:15:54 +00:00
}
@override
Future<http.Response> put(String url,
{body, Map<String, String> headers}) async {
2017-01-25 23:25:31 +00:00
return sendUnstreamed('PUT', _join(url), headers, body);
2016-12-10 17:15:54 +00:00
}
2016-12-09 00:24:07 +00:00
}
class BaseAngelService extends Service {
@override
2017-01-25 23:25:31 +00:00
final BaseAngelClient app;
2016-12-09 00:24:07 +00:00
final String basePath;
final http.BaseClient client;
2016-12-13 16:35:35 +00:00
final AngelDeserializer deserializer;
2016-12-09 00:24:07 +00:00
2016-12-13 16:35:35 +00:00
BaseAngelService(this.client, this.app, this.basePath, {this.deserializer});
deserialize(x) {
return deserializer != null ? deserializer(x) : x;
}
2016-12-09 00:24:07 +00:00
makeBody(x) {
return JSON.encode(x);
}
Future<http.StreamedResponse> send(http.BaseRequest request) {
if (app.authToken != null && app.authToken.isNotEmpty) {
request.headers['Authorization'] = 'Bearer ${app.authToken}';
}
return client.send(request);
}
@override
2017-02-12 19:58:18 +00:00
Future index([Map params]) async {
2017-01-25 23:25:31 +00:00
final response = await app.sendUnstreamed(
2016-12-09 00:24:07 +00:00
'GET', '$basePath/${_buildQuery(params)}', _readHeaders);
try {
if (response.statusCode != 200) {
throw failure(response);
}
final json = JSON.decode(response.body);
2017-02-12 19:58:18 +00:00
if (json is! List) return json;
2016-12-13 16:35:35 +00:00
return json.map(deserialize).toList();
2016-12-09 00:24:07 +00:00
} catch (e, st) {
throw failure(response, error: e, stack: st);
}
}
@override
Future read(id, [Map params]) async {
2017-01-25 23:25:31 +00:00
final response = await app.sendUnstreamed(
2016-12-09 00:24:07 +00:00
'GET', '$basePath/$id${_buildQuery(params)}', _readHeaders);
try {
if (response.statusCode != 200) {
throw failure(response);
}
2016-12-13 16:35:35 +00:00
return deserialize(JSON.decode(response.body));
2016-12-09 00:24:07 +00:00
} catch (e, st) {
throw failure(response, error: e, stack: st);
}
}
@override
Future create(data, [Map params]) async {
2017-01-25 23:25:31 +00:00
final response = await app.sendUnstreamed('POST',
2016-12-10 14:45:22 +00:00
'$basePath/${_buildQuery(params)}', _writeHeaders, makeBody(data));
2016-12-09 00:24:07 +00:00
try {
if (response.statusCode != 200) {
throw failure(response);
}
2016-12-13 16:35:35 +00:00
return deserialize(JSON.decode(response.body));
2016-12-09 00:24:07 +00:00
} catch (e, st) {
throw failure(response, error: e, stack: st);
}
}
@override
Future modify(id, data, [Map params]) async {
2017-01-25 23:25:31 +00:00
final response = await app.sendUnstreamed('PATCH',
2016-12-10 14:45:22 +00:00
'$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data));
2016-12-09 00:24:07 +00:00
try {
if (response.statusCode != 200) {
throw failure(response);
}
2016-12-13 16:35:35 +00:00
return deserialize(JSON.decode(response.body));
2016-12-09 00:24:07 +00:00
} catch (e, st) {
throw failure(response, error: e, stack: st);
}
}
@override
Future update(id, data, [Map params]) async {
2017-01-25 23:25:31 +00:00
final response = await app.sendUnstreamed('POST',
2016-12-10 14:45:22 +00:00
'$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data));
2016-12-09 00:24:07 +00:00
try {
if (response.statusCode != 200) {
throw failure(response);
}
2016-12-13 16:35:35 +00:00
return deserialize(JSON.decode(response.body));
2016-12-09 00:24:07 +00:00
} catch (e, st) {
throw failure(response, error: e, stack: st);
}
}
@override
Future remove(id, [Map params]) async {
2017-01-25 23:25:31 +00:00
final response = await app.sendUnstreamed(
2016-12-09 00:24:07 +00:00
'DELETE', '$basePath/$id${_buildQuery(params)}', _readHeaders);
try {
if (response.statusCode != 200) {
throw failure(response);
}
2016-12-13 16:35:35 +00:00
return deserialize(JSON.decode(response.body));
2016-12-09 00:24:07 +00:00
} catch (e, st) {
throw failure(response, error: e, stack: st);
}
}
}