Common code :)
This commit is contained in:
parent
c220a48830
commit
90af9f9bb6
8 changed files with 374 additions and 294 deletions
1
.travis.yml
Normal file
1
.travis.yml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
language: dart
|
|
@ -1,4 +1,8 @@
|
||||||
# angel_client
|
# angel_client
|
||||||
|
|
||||||
|
[![pub 1.0.0-dev+16](https://img.shields.io/badge/pub-1.0.0--dev+16-red.svg)](https://pub.dartlang.org/packages/angel_framework)
|
||||||
|
![build status](https://travis-ci.org/angel-dart/client.svg)
|
||||||
|
|
||||||
Client library for the Angel framework.
|
Client library for the Angel framework.
|
||||||
|
|
||||||
# Isomorphic
|
# Isomorphic
|
||||||
|
|
|
@ -2,13 +2,15 @@
|
||||||
library angel_client;
|
library angel_client;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'auth_types.dart' as auth_types;
|
import 'dart:convert';
|
||||||
|
export 'package:angel_framework/src/http/angel_http_exception.dart';
|
||||||
|
|
||||||
/// A function that configures an [Angel] client in some way.
|
/// A function that configures an [Angel] client in some way.
|
||||||
typedef Future AngelConfigurer(Angel app);
|
typedef Future AngelConfigurer(Angel app);
|
||||||
|
|
||||||
/// Represents an Angel server that we are querying.
|
/// Represents an Angel server that we are querying.
|
||||||
abstract class Angel {
|
abstract class Angel {
|
||||||
|
String get authToken;
|
||||||
String basePath;
|
String basePath;
|
||||||
|
|
||||||
Angel(String this.basePath);
|
Angel(String this.basePath);
|
||||||
|
@ -28,11 +30,33 @@ abstract class Angel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the result of authentication with an Angel server.
|
/// Represents the result of authentication with an Angel server.
|
||||||
abstract class AngelAuthResult {
|
class AngelAuthResult {
|
||||||
Map<String, dynamic> get data;
|
String _token;
|
||||||
String get token;
|
final Map<String, dynamic> data = {};
|
||||||
|
String get token => _token;
|
||||||
|
|
||||||
Map<String, dynamic> toJson();
|
AngelAuthResult({String token, Map<String, dynamic> data: const {}}) {
|
||||||
|
_token = token;
|
||||||
|
this.data.addAll(data ?? {});
|
||||||
|
}
|
||||||
|
|
||||||
|
factory AngelAuthResult.fromMap(Map data) {
|
||||||
|
final result = new AngelAuthResult();
|
||||||
|
|
||||||
|
if (data is Map && data.containsKey('token') && data['token'] is String)
|
||||||
|
result._token = data['token'];
|
||||||
|
|
||||||
|
if (data is Map) result.data.addAll(data['data'] ?? {});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory AngelAuthResult.fromJson(String json) =>
|
||||||
|
new AngelAuthResult.fromMap(JSON.decode(json));
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'token': token, 'data': data};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries a service on an Angel server, with the same API.
|
/// Queries a service on an Angel server, with the same API.
|
||||||
|
|
278
lib/base_angel_client.dart
Normal file
278
lib/base_angel_client.dart
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
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 'package:merge_map/merge_map.dart';
|
||||||
|
import 'angel_client.dart';
|
||||||
|
import 'auth_types.dart' as auth_types;
|
||||||
|
|
||||||
|
const Map<String, String> _readHeaders = const {'Accept': 'application/json'};
|
||||||
|
final Map<String, String> _writeHeaders = mergeMap([
|
||||||
|
_readHeaders,
|
||||||
|
const {'Content-Type': 'application/json'}
|
||||||
|
]);
|
||||||
|
|
||||||
|
_buildQuery(Map params) {
|
||||||
|
if (params == null || params.isEmpty) return "";
|
||||||
|
|
||||||
|
List<String> query = [];
|
||||||
|
|
||||||
|
params.forEach((k, v) {
|
||||||
|
query.add('$k=$v');
|
||||||
|
});
|
||||||
|
|
||||||
|
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';
|
||||||
|
final response = await client.post(url,
|
||||||
|
headers: mergeMap([
|
||||||
|
_writeHeaders,
|
||||||
|
{'Authorization': 'Bearer ${credentials['token']}'}
|
||||||
|
]));
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw failure(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
final json = JSON.decode(response.body);
|
||||||
|
|
||||||
|
if (json is! Map ||
|
||||||
|
!json.containsKey('data') ||
|
||||||
|
!json.containsKey('token')) {
|
||||||
|
throw new AngelHttpException.NotAuthenticated(
|
||||||
|
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')) {
|
||||||
|
throw new AngelHttpException.NotAuthenticated(
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Service service(String path, {Type type}) {
|
||||||
|
String uri = path.replaceAll(new RegExp(r"(^/)|(/+$)"), "");
|
||||||
|
return new BaseAngelService(client, this, '$basePath/$uri');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BaseAngelService extends Service {
|
||||||
|
@override
|
||||||
|
final Angel app;
|
||||||
|
final String basePath;
|
||||||
|
final http.BaseClient client;
|
||||||
|
|
||||||
|
BaseAngelService(this.client, this.app, this.basePath);
|
||||||
|
|
||||||
|
makeBody(x) {
|
||||||
|
return JSON.encode(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 (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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
Future<List> index([Map params]) async {
|
||||||
|
final response = await sendUnstreamed(
|
||||||
|
'GET', '$basePath/${_buildQuery(params)}', _readHeaders);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw failure(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
final json = JSON.decode(response.body);
|
||||||
|
|
||||||
|
if (json is! List) {
|
||||||
|
throw failure(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
} catch (e, st) {
|
||||||
|
throw failure(response, error: e, stack: st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future read(id, [Map params]) async {
|
||||||
|
final response = await sendUnstreamed(
|
||||||
|
'GET', '$basePath/$id${_buildQuery(params)}', _readHeaders);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw failure(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.decode(response.body);
|
||||||
|
} catch (e, st) {
|
||||||
|
throw failure(response, error: e, stack: st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future create(data, [Map params]) async {
|
||||||
|
final response = await sendUnstreamed(
|
||||||
|
'POST', '$basePath/${_buildQuery(params)}', _writeHeaders, makeBody(data));
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw failure(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.decode(response.body);
|
||||||
|
} catch (e, st) {
|
||||||
|
throw failure(response, error: e, stack: st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future modify(id, data, [Map params]) async {
|
||||||
|
final response = await sendUnstreamed(
|
||||||
|
'PATCH', '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data));
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw failure(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.decode(response.body);
|
||||||
|
} catch (e, st) {
|
||||||
|
throw failure(response, error: e, stack: st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future update(id, data, [Map params]) async {
|
||||||
|
final response = await sendUnstreamed(
|
||||||
|
'POST', '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data));
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw failure(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.decode(response.body);
|
||||||
|
} catch (e, st) {
|
||||||
|
throw failure(response, error: e, stack: st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future remove(id, [Map params]) async {
|
||||||
|
final response = await sendUnstreamed(
|
||||||
|
'DELETE', '$basePath/$id${_buildQuery(params)}', _readHeaders);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw failure(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.decode(response.body);
|
||||||
|
} catch (e, st) {
|
||||||
|
throw failure(response, error: e, stack: st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
224
lib/browser.dart
224
lib/browser.dart
|
@ -1,54 +1,22 @@
|
||||||
/// Browser library for the Angel framework.
|
/// Browser library for the Angel framework.
|
||||||
library angel_client.browser;
|
library angel_client.browser;
|
||||||
|
|
||||||
import 'dart:async' show Completer, Future;
|
import 'dart:async' show Future;
|
||||||
import 'dart:convert' show JSON;
|
import 'dart:convert' show JSON;
|
||||||
import 'dart:html' show HttpRequest, window;
|
import 'dart:html' show window;
|
||||||
|
import 'package:http/browser_client.dart' as http;
|
||||||
import 'angel_client.dart';
|
import 'angel_client.dart';
|
||||||
import 'auth_types.dart' as auth_types;
|
import 'auth_types.dart' as auth_types;
|
||||||
|
import 'base_angel_client.dart';
|
||||||
export 'angel_client.dart';
|
export 'angel_client.dart';
|
||||||
|
|
||||||
_buildQuery(Map params) {
|
|
||||||
if (params == null || params == {}) return "";
|
|
||||||
|
|
||||||
String result = "";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
_send(HttpRequest request, [data]) {
|
|
||||||
final completer = new Completer<HttpRequest>();
|
|
||||||
|
|
||||||
request
|
|
||||||
..onLoadEnd.listen((_) {
|
|
||||||
completer.complete(request.response);
|
|
||||||
})
|
|
||||||
..onError.listen((_) {
|
|
||||||
try {
|
|
||||||
throw new Exception(
|
|
||||||
'Request failed with status code ${request.status}.');
|
|
||||||
} catch (e, st) {
|
|
||||||
completer.completeError(e, st);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data == null)
|
|
||||||
request.send();
|
|
||||||
else if (data is String)
|
|
||||||
request.send(data);
|
|
||||||
else
|
|
||||||
request.send(JSON.encode(data));
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queries an Angel server via REST.
|
/// Queries an Angel server via REST.
|
||||||
class Rest extends Angel {
|
class Rest extends BaseAngelClient {
|
||||||
String _authToken;
|
Rest(String basePath) : super(new http.BrowserClient(), basePath);
|
||||||
|
|
||||||
Rest(String basePath) : super(basePath);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future authenticate(
|
Future<AngelAuthResult> authenticate(
|
||||||
{String type,
|
{String type: auth_types.LOCAL,
|
||||||
credentials,
|
credentials,
|
||||||
String authEndpoint: '/auth',
|
String authEndpoint: '/auth',
|
||||||
String reviveEndpoint: '/auth/token'}) async {
|
String reviveEndpoint: '/auth/token'}) async {
|
||||||
|
@ -58,177 +26,23 @@ class Rest extends Angel {
|
||||||
'Cannot revive token from localStorage - there is none.');
|
'Cannot revive token from localStorage - there is none.');
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = new _AngelAuthResultImpl(
|
|
||||||
token: JSON.decode(window.localStorage['token']),
|
|
||||||
data: JSON.decode(window.localStorage['user']));
|
|
||||||
final completer = new Completer();
|
|
||||||
final request = new HttpRequest()..responseType = 'json';
|
|
||||||
request.open('POST', '$basePath$reviveEndpoint');
|
|
||||||
request.setRequestHeader('Accept', 'application/json');
|
|
||||||
request.setRequestHeader('Content-Type', 'application/json');
|
|
||||||
request.setRequestHeader('Authorization', 'Bearer ${result.token}');
|
|
||||||
|
|
||||||
request
|
|
||||||
..onLoadEnd.listen((_) {
|
|
||||||
final result = new _AngelAuthResultImpl.fromMap(request.response);
|
|
||||||
_authToken = result.token;
|
|
||||||
window.localStorage['token'] = JSON.encode(result.token);
|
|
||||||
window.localStorage['user'] = JSON.encode(result.data);
|
|
||||||
completer.complete(result);
|
|
||||||
})
|
|
||||||
..onError.listen((_) {
|
|
||||||
try {
|
try {
|
||||||
throw new Exception(
|
final result = await super.authenticate(
|
||||||
'Request failed with status code ${request.status}.');
|
credentials: {'token': JSON.decode(window.localStorage['token'])},
|
||||||
} catch (e, st) {
|
reviveEndpoint: reviveEndpoint);
|
||||||
completer.completeError(e, st);
|
window.localStorage['token'] = JSON.encode(authToken = result.token);
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
request.send(JSON.encode(result));
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
final url = '$basePath$authEndpoint/$type';
|
|
||||||
|
|
||||||
if (type == auth_types.LOCAL) {
|
|
||||||
final completer = new Completer();
|
|
||||||
final request = new HttpRequest();
|
|
||||||
request.open('POST', url);
|
|
||||||
request.responseType = 'json';
|
|
||||||
request.setRequestHeader("Accept", "application/json");
|
|
||||||
request.setRequestHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
request
|
|
||||||
..onLoadEnd.listen((_) {
|
|
||||||
final result = new _AngelAuthResultImpl.fromMap(request.response);
|
|
||||||
_authToken = result.token;
|
|
||||||
window.localStorage['token'] = JSON.encode(result.token);
|
|
||||||
window.localStorage['user'] = JSON.encode(result.data);
|
window.localStorage['user'] = JSON.encode(result.data);
|
||||||
completer.complete(result);
|
return result;
|
||||||
})
|
|
||||||
..onError.listen((_) {
|
|
||||||
try {
|
|
||||||
throw new Exception(
|
|
||||||
'Request failed with status code ${request.status}.');
|
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
completer.completeError(e, st);
|
throw new AngelHttpException(e,
|
||||||
|
message: 'Failed to revive auth token.', stackTrace: st);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (credentials == null)
|
|
||||||
request.send();
|
|
||||||
else
|
|
||||||
request.send(JSON.encode(credentials));
|
|
||||||
|
|
||||||
return completer.future;
|
|
||||||
} else {
|
} else {
|
||||||
throw new Exception('angel_client cannot authenticate as "$type" yet.');
|
final result = await super.authenticate(
|
||||||
}
|
type: type, credentials: credentials, authEndpoint: authEndpoint);
|
||||||
}
|
window.localStorage['token'] = JSON.encode(authToken = result.token);
|
||||||
|
window.localStorage['user'] = JSON.encode(result.data);
|
||||||
@override
|
|
||||||
RestService service(String path, {Type type}) {
|
|
||||||
String uri = path.replaceAll(new RegExp(r"(^\/)|(\/+$)"), "");
|
|
||||||
return new _RestServiceImpl(this, "$basePath/$uri");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class RestService extends Service {
|
|
||||||
RestService._(String basePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AngelAuthResultImpl implements AngelAuthResult {
|
|
||||||
String _token;
|
|
||||||
final Map<String, dynamic> data = {};
|
|
||||||
String get token => _token;
|
|
||||||
|
|
||||||
_AngelAuthResultImpl({token, Map<String, dynamic> data: const {}}) {
|
|
||||||
if (token is String) _token = token;
|
|
||||||
|
|
||||||
this.data.addAll(data ?? {});
|
|
||||||
}
|
|
||||||
|
|
||||||
factory _AngelAuthResultImpl.fromMap(Map data) {
|
|
||||||
final result = new _AngelAuthResultImpl();
|
|
||||||
|
|
||||||
if (data is Map && data.containsKey('token') && data['token'] is String)
|
|
||||||
result._token = data['token'];
|
|
||||||
|
|
||||||
if (data is Map) result.data.addAll(data['data'] ?? {});
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {'token': token, 'data': data};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queries an Angel service via REST.
|
|
||||||
class _RestServiceImpl extends RestService {
|
|
||||||
final Rest app;
|
|
||||||
String _basePath;
|
|
||||||
String get basePath => _basePath;
|
|
||||||
|
|
||||||
_RestServiceImpl(this.app, String basePath) : super._(basePath) {
|
|
||||||
_basePath = basePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
_makeBody(data) {
|
|
||||||
return JSON.encode(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<HttpRequest> buildRequest(String url,
|
|
||||||
{String method: "POST", bool write: true}) async {
|
|
||||||
HttpRequest request = new HttpRequest();
|
|
||||||
request.open(method, url);
|
|
||||||
request.responseType = "json";
|
|
||||||
request.setRequestHeader("Accept", "application/json");
|
|
||||||
if (write) request.setRequestHeader("Content-Type", "application/json");
|
|
||||||
if (app._authToken != null)
|
|
||||||
request.setRequestHeader("Authorization", "Bearer ${app._authToken}");
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List> index([Map params]) async {
|
|
||||||
final request = await buildRequest('$basePath/${_buildQuery(params)}',
|
|
||||||
method: 'GET', write: false);
|
|
||||||
return await _send(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future read(id, [Map params]) async {
|
|
||||||
final request = await buildRequest('$basePath/$id${_buildQuery(params)}',
|
|
||||||
method: 'GET', write: false);
|
|
||||||
return await _send(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future create(data, [Map params]) async {
|
|
||||||
final request = await buildRequest("$basePath/${_buildQuery(params)}");
|
|
||||||
return await _send(request, _makeBody(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future modify(id, data, [Map params]) async {
|
|
||||||
final request = await buildRequest("$basePath/$id${_buildQuery(params)}",
|
|
||||||
method: "PATCH");
|
|
||||||
return await _send(request, _makeBody(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future update(id, data, [Map params]) async {
|
|
||||||
final request = await buildRequest("$basePath/$id${_buildQuery(params)}");
|
|
||||||
return await _send(request, _makeBody(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future remove(id, [Map params]) async {
|
|
||||||
final request = await buildRequest("$basePath/$id${_buildQuery(params)}",
|
|
||||||
method: "DELETE");
|
|
||||||
return await _send(request);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
111
lib/io.dart
111
lib/io.dart
|
@ -2,109 +2,68 @@
|
||||||
library angel_client.cli;
|
library angel_client.cli;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert' show JSON;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:http/http.dart';
|
|
||||||
import 'package:json_god/json_god.dart' as god;
|
import 'package:json_god/json_god.dart' as god;
|
||||||
import 'angel_client.dart';
|
import 'angel_client.dart';
|
||||||
|
import 'base_angel_client.dart';
|
||||||
export 'angel_client.dart';
|
export 'angel_client.dart';
|
||||||
|
|
||||||
_buildQuery(Map params) {
|
|
||||||
if (params == null || params == {})
|
|
||||||
return "";
|
|
||||||
|
|
||||||
String result = "";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Map _readHeaders = const {
|
|
||||||
"Accept": "application/json"
|
|
||||||
};
|
|
||||||
|
|
||||||
const Map _writeHeaders = const {
|
|
||||||
"Accept": "application/json",
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Queries an Angel server via REST.
|
/// Queries an Angel server via REST.
|
||||||
class Rest extends Angel {
|
class Rest extends BaseAngelClient {
|
||||||
BaseClient client;
|
Rest(String path) : super(new http.Client(), path);
|
||||||
|
|
||||||
Rest(String path, BaseClient this.client) :super(path);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RestService service(String path, {Type type}) {
|
Service service(String path, {Type type}) {
|
||||||
String uri = path.replaceAll(new RegExp(r"(^\/)|(\/+$)"), "");
|
String uri = path.replaceAll(new RegExp(r"(^/)|(/+$)"), "");
|
||||||
return new RestService._base("$basePath/$uri", client, type)
|
return new RestService(client, this, "$basePath/$uri", type);
|
||||||
..app = this;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries an Angel service via REST.
|
/// Queries an Angel service via REST.
|
||||||
class RestService extends Service {
|
class RestService extends BaseAngelService {
|
||||||
String basePath;
|
final Type type;
|
||||||
BaseClient client;
|
|
||||||
Type outputType;
|
|
||||||
|
|
||||||
RestService._base(Pattern path, BaseClient this.client,
|
RestService(http.BaseClient client, Angel app, String url, this.type)
|
||||||
Type this.outputType) {
|
: super(client, app, url);
|
||||||
this.basePath = (path is RegExp) ? path.pattern : path;
|
|
||||||
|
deserialize(x) {
|
||||||
|
if (type != null) {
|
||||||
|
return god.deserializeDatum(x, outputType: type);
|
||||||
}
|
}
|
||||||
|
|
||||||
_makeBody(data) {
|
return x;
|
||||||
if (outputType == null)
|
}
|
||||||
return JSON.encode(data);
|
|
||||||
else return god.serialize(data);
|
@override
|
||||||
|
makeBody(x) {
|
||||||
|
if (type != null) {
|
||||||
|
return super.makeBody(god.serializeObject(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.makeBody(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List> index([Map params]) async {
|
Future<List> index([Map params]) async {
|
||||||
var response = await client.get(
|
final items = await super.index(params);
|
||||||
"$basePath/${_buildQuery(params)}", headers: _readHeaders);
|
return items.map(deserialize).toList();
|
||||||
|
|
||||||
if (outputType == null)
|
|
||||||
return god.deserialize(response.body);
|
|
||||||
|
|
||||||
else {
|
|
||||||
return JSON.decode(response.body).map((x) =>
|
|
||||||
god.deserializeDatum(x, outputType: outputType)).toList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future read(id, [Map params]) async {
|
Future read(id, [Map params]) => super.read(id, params).then(deserialize);
|
||||||
var response = await client.get(
|
|
||||||
"$basePath/$id${_buildQuery(params)}", headers: _readHeaders);
|
|
||||||
return god.deserialize(response.body, outputType: outputType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future create(data, [Map params]) async {
|
Future create(data, [Map params]) =>
|
||||||
var response = await client.post(
|
super.create(data, params).then(deserialize);
|
||||||
"$basePath/${_buildQuery(params)}", body: _makeBody(data),
|
|
||||||
headers: _writeHeaders);
|
|
||||||
return god.deserialize(response.body, outputType: outputType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future modify(id, data, [Map params]) async {
|
Future modify(id, data, [Map params]) =>
|
||||||
var response = await client.patch(
|
super.modify(id, data, params).then(deserialize);
|
||||||
"$basePath/$id${_buildQuery(params)}", body: _makeBody(data),
|
|
||||||
headers: _writeHeaders);
|
|
||||||
return god.deserialize(response.body, outputType: outputType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future update(id, data, [Map params]) async {
|
Future update(id, data, [Map params]) =>
|
||||||
var response = await client.patch(
|
super.update(id, data, params).then(deserialize);
|
||||||
"$basePath/$id${_buildQuery(params)}", body: _makeBody(data),
|
|
||||||
headers: _writeHeaders);
|
|
||||||
return god.deserialize(response.body, outputType: outputType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future remove(id, [Map params]) async {
|
Future remove(id, [Map params]) => super.remove(id, params).then(deserialize);
|
||||||
var response = await client.delete(
|
|
||||||
"$basePath/$id${_buildQuery(params)}", headers: _readHeaders);
|
|
||||||
return god.deserialize(response.body, outputType: outputType);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
name: angel_client
|
name: angel_client
|
||||||
version: 1.0.0-dev+15
|
version: 1.0.0-dev+16
|
||||||
description: Client library for the Angel framework.
|
description: Client library for the Angel framework.
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
homepage: https://github.com/angel-dart/angel_client
|
homepage: https://github.com/angel-dart/angel_client
|
||||||
dependencies:
|
dependencies:
|
||||||
|
angel_framework: ">=1.0.0-dev <2.0.0"
|
||||||
http: ">= 0.11.3 < 0.12.0"
|
http: ">= 0.11.3 < 0.12.0"
|
||||||
json_god: ">=2.0.0-beta <3.0.0"
|
json_god: ">=2.0.0-beta <3.0.0"
|
||||||
merge_map: ">=1.0.0 <2.0.0"
|
merge_map: ">=1.0.0 <2.0.0"
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
angel_framework: ">=1.0.0-dev <2.0.0"
|
|
||||||
test: ">= 0.12.13 < 0.13.0"
|
test: ">= 0.12.13 < 0.13.0"
|
||||||
|
|
|
@ -22,7 +22,7 @@ main() {
|
||||||
serverApp.use("/postcards", new server.MemoryService<Postcard>());
|
serverApp.use("/postcards", new server.MemoryService<Postcard>());
|
||||||
serverPostcards = serverApp.service("postcards");
|
serverPostcards = serverApp.service("postcards");
|
||||||
|
|
||||||
clientApp = new client.Rest(url, new http.Client());
|
clientApp = new client.Rest(url);
|
||||||
clientPostcards = clientApp.service("postcards");
|
clientPostcards = clientApp.service("postcards");
|
||||||
clientTypedPostcards = clientApp.service("postcards", type: Postcard);
|
clientTypedPostcards = clientApp.service("postcards", type: Postcard);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue