Migrated angel_client to 4.0.0
This commit is contained in:
parent
5e9172aca9
commit
037e82d699
13 changed files with 220 additions and 200 deletions
|
@ -15,7 +15,7 @@
|
|||
* Updated angel_configuration to 4.0.0 (6/8 test passed)
|
||||
* Updated angel_validate to 4.0.0 (6/7 test passed)
|
||||
* Updated json_god to 4.0.0 (13/13 test passed)
|
||||
* Updated angel_client to 3.0.0 (in progress)
|
||||
* Updated angel_client to 4.0.0 (6/13 test passed)
|
||||
* Updated angel_websocket to 3.0.0 (in progress)
|
||||
* Updated test to 3.0.0 (in progress)
|
||||
* Updated jael to 3.0.0 (in progress)
|
||||
|
|
|
@ -6,16 +6,16 @@ Future doSomething(Angel app) async {
|
|||
.service<String, Map<String, dynamic>>('api/users')
|
||||
.map(User.fromMap, User.toMap);
|
||||
|
||||
var users = await userService.index();
|
||||
var users = await (userService.index() as FutureOr<List<User>>);
|
||||
print('Name: ${users.first.name}');
|
||||
}
|
||||
|
||||
class User {
|
||||
final String name;
|
||||
final String? name;
|
||||
|
||||
User({this.name});
|
||||
|
||||
static User fromMap(Map data) => User(name: data['name'] as String);
|
||||
static User fromMap(Map data) => User(name: data['name'] as String?);
|
||||
|
||||
static Map<String, String> toMap(User user) => {'name': user.name};
|
||||
static Map<String, String?> toMap(User user) => {'name': user.name};
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@ export 'package:angel_http_exception/angel_http_exception.dart';
|
|||
import 'package:meta/meta.dart';
|
||||
|
||||
/// A function that configures an [Angel] client in some way.
|
||||
typedef FutureOr<void> AngelConfigurer(Angel app);
|
||||
typedef AngelConfigurer = FutureOr<void> Function(Angel app);
|
||||
|
||||
/// A function that deserializes data received from the server.
|
||||
///
|
||||
/// This is only really necessary in the browser, where `json_god`
|
||||
/// doesn't work.
|
||||
typedef T AngelDeserializer<T>(x);
|
||||
typedef AngelDeserializer<T> = T? Function(dynamic x);
|
||||
|
||||
/// Represents an Angel server that we are querying.
|
||||
abstract class Angel extends http.BaseClient {
|
||||
|
@ -23,7 +23,7 @@ abstract class Angel extends http.BaseClient {
|
|||
/// that is automatically attached to every request sent.
|
||||
///
|
||||
/// This is designed with `package:angel_auth` in mind.
|
||||
String authToken;
|
||||
String? authToken;
|
||||
|
||||
/// The root URL at which the target server.
|
||||
final Uri baseUrl;
|
||||
|
@ -46,7 +46,7 @@ abstract class Angel extends http.BaseClient {
|
|||
///
|
||||
/// The given [credentials] are sent to server as-is; the request body is sent as JSON.
|
||||
Future<AngelAuthResult> authenticate(
|
||||
{@required String type,
|
||||
{required String type,
|
||||
credentials,
|
||||
String authEndpoint = '/auth',
|
||||
@deprecated String reviveEndpoint = '/auth/token'});
|
||||
|
@ -85,57 +85,59 @@ abstract class Angel extends http.BaseClient {
|
|||
/// You can pass a custom [deserializer], which is typically necessary in cases where
|
||||
/// `dart:mirrors` does not exist.
|
||||
Service<Id, Data> service<Id, Data>(String path,
|
||||
{@deprecated Type type, AngelDeserializer<Data> deserializer});
|
||||
{@deprecated Type? type, AngelDeserializer<Data>? deserializer});
|
||||
|
||||
//@override
|
||||
//Future<http.Response> delete(url, {Map<String, String> headers});
|
||||
|
||||
@override
|
||||
Future<http.Response> get(url, {Map<String, String> headers});
|
||||
Future<http.Response> get(url, {Map<String, String>? headers});
|
||||
|
||||
@override
|
||||
Future<http.Response> head(url, {Map<String, String> headers});
|
||||
Future<http.Response> head(url, {Map<String, String>? headers});
|
||||
|
||||
@override
|
||||
Future<http.Response> patch(url,
|
||||
{body, Map<String, String> headers, Encoding encoding});
|
||||
{body, Map<String, String>? headers, Encoding? encoding});
|
||||
|
||||
@override
|
||||
Future<http.Response> post(url,
|
||||
{body, Map<String, String> headers, Encoding encoding});
|
||||
{body, Map<String, String>? headers, Encoding? encoding});
|
||||
|
||||
@override
|
||||
Future<http.Response> put(url,
|
||||
{body, Map<String, String> headers, Encoding encoding});
|
||||
{body, Map<String, String>? headers, Encoding? encoding});
|
||||
}
|
||||
|
||||
/// Represents the result of authentication with an Angel server.
|
||||
class AngelAuthResult {
|
||||
String _token;
|
||||
String? _token;
|
||||
final Map<String, dynamic> data = {};
|
||||
|
||||
/// The JSON Web token that was sent with this response.
|
||||
String get token => _token;
|
||||
String? get token => _token;
|
||||
|
||||
AngelAuthResult({String token, Map<String, dynamic> data = const {}}) {
|
||||
AngelAuthResult({String? token, Map<String, dynamic> data = const {}}) {
|
||||
_token = token;
|
||||
this.data.addAll(data ?? {});
|
||||
this.data.addAll(data);
|
||||
}
|
||||
|
||||
/// Attempts to deserialize a response from a [Map].
|
||||
factory AngelAuthResult.fromMap(Map data) {
|
||||
factory AngelAuthResult.fromMap(Map? data) {
|
||||
final result = AngelAuthResult();
|
||||
|
||||
if (data is Map && data.containsKey('token') && data['token'] is String)
|
||||
if (data is Map && data.containsKey('token') && data['token'] is String) {
|
||||
result._token = data['token'].toString();
|
||||
}
|
||||
|
||||
if (data is Map)
|
||||
result.data.addAll((data['data'] as Map<String, dynamic>) ?? {});
|
||||
if (data is Map) {
|
||||
result.data.addAll((data['data'] as Map<String, dynamic>?) ?? {});
|
||||
}
|
||||
|
||||
if (result.token == null) {
|
||||
throw FormatException(
|
||||
'The required "token" field was not present in the given data.');
|
||||
} else if (data['data'] is! Map) {
|
||||
} else if (data!['data'] is! Map) {
|
||||
throw FormatException(
|
||||
'The required "data" field in the given data was not a map; instead, it was ${data['data']}.');
|
||||
}
|
||||
|
@ -145,7 +147,7 @@ class AngelAuthResult {
|
|||
|
||||
/// Attempts to deserialize a response from a [String].
|
||||
factory AngelAuthResult.fromJson(String s) =>
|
||||
AngelAuthResult.fromMap(json.decode(s) as Map);
|
||||
AngelAuthResult.fromMap(json.decode(s) as Map?);
|
||||
|
||||
/// Converts this instance into a JSON-friendly representation.
|
||||
Map<String, dynamic> toJson() {
|
||||
|
@ -179,22 +181,22 @@ abstract class Service<Id, Data> {
|
|||
Future close();
|
||||
|
||||
/// Retrieves all resources.
|
||||
Future<List<Data>> index([Map<String, dynamic> params]);
|
||||
Future<List<Data>?> index([Map<String, dynamic>? params]);
|
||||
|
||||
/// Retrieves the desired resource.
|
||||
Future<Data> read(Id id, [Map<String, dynamic> params]);
|
||||
Future<Data> read(Id id, [Map<String, dynamic>? params]);
|
||||
|
||||
/// Creates a resource.
|
||||
Future<Data> create(Data data, [Map<String, dynamic> params]);
|
||||
Future<Data> create(Data data, [Map<String, dynamic>? params]);
|
||||
|
||||
/// Modifies a resource.
|
||||
Future<Data> modify(Id id, Data data, [Map<String, dynamic> params]);
|
||||
Future<Data> modify(Id id, Data data, [Map<String, dynamic>? params]);
|
||||
|
||||
/// Overwrites a resource.
|
||||
Future<Data> update(Id id, Data data, [Map<String, dynamic> params]);
|
||||
Future<Data> update(Id id, Data data, [Map<String, dynamic>? params]);
|
||||
|
||||
/// Removes the given resource.
|
||||
Future<Data> remove(Id id, [Map<String, dynamic> params]);
|
||||
Future<Data> remove(Id id, [Map<String, dynamic>? params]);
|
||||
|
||||
/// Creates a [Service] that wraps over this one, and maps input and output using two converter functions.
|
||||
///
|
||||
|
@ -218,17 +220,17 @@ class _MappedService<Id, Data, U> extends Service<Id, U> {
|
|||
Future close() => Future.value();
|
||||
|
||||
@override
|
||||
Future<U> create(U data, [Map<String, dynamic> params]) {
|
||||
Future<U> create(U data, [Map<String, dynamic>? params]) {
|
||||
return inner.create(decoder(data)).then(encoder);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<U>> index([Map<String, dynamic> params]) {
|
||||
return inner.index(params).then((l) => l.map(encoder).toList());
|
||||
Future<List<U>> index([Map<String, dynamic>? params]) {
|
||||
return inner.index(params).then((l) => l!.map(encoder).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<U> modify(Id id, U data, [Map<String, dynamic> params]) {
|
||||
Future<U> modify(Id id, U data, [Map<String, dynamic>? params]) {
|
||||
return inner.modify(id, decoder(data), params).then(encoder);
|
||||
}
|
||||
|
||||
|
@ -252,17 +254,17 @@ class _MappedService<Id, Data, U> extends Service<Id, U> {
|
|||
Stream<U> get onUpdated => inner.onUpdated.map(encoder);
|
||||
|
||||
@override
|
||||
Future<U> read(Id id, [Map<String, dynamic> params]) {
|
||||
Future<U> read(Id id, [Map<String, dynamic>? params]) {
|
||||
return inner.read(id, params).then(encoder);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<U> remove(Id id, [Map<String, dynamic> params]) {
|
||||
Future<U> remove(Id id, [Map<String, dynamic>? params]) {
|
||||
return inner.remove(id, params).then(encoder);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<U> update(Id id, U data, [Map<String, dynamic> params]) {
|
||||
Future<U> update(Id id, U data, [Map<String, dynamic>? params]) {
|
||||
return inner.update(id, decoder(data), params).then(encoder);
|
||||
}
|
||||
}
|
||||
|
@ -275,9 +277,9 @@ class ServiceList<Id, Data> extends DelegatingList<Data> {
|
|||
/// A function used to compare the ID's two items for equality.
|
||||
///
|
||||
/// Defaults to comparing the [idField] of `Map` instances.
|
||||
Equality<Data> get equality => _equality;
|
||||
Equality<Data>? get equality => _equality;
|
||||
|
||||
Equality<Data> _equality;
|
||||
Equality<Data>? _equality;
|
||||
|
||||
final Service<Id, Data> service;
|
||||
|
||||
|
@ -285,15 +287,16 @@ class ServiceList<Id, Data> extends DelegatingList<Data> {
|
|||
|
||||
final List<StreamSubscription> _subs = [];
|
||||
|
||||
ServiceList(this.service, {this.idField = 'id', Equality<Data> equality})
|
||||
ServiceList(this.service, {this.idField = 'id', Equality<Data>? equality})
|
||||
: super([]) {
|
||||
_equality = equality;
|
||||
_equality ??= EqualityBy<Data, Id>((map) {
|
||||
if (map is Map)
|
||||
return map[idField ?? 'id'] as Id;
|
||||
else
|
||||
_equality ??= EqualityBy<Data, Id?>((map) {
|
||||
if (map is Map) {
|
||||
return map[idField] as Id?;
|
||||
} else {
|
||||
throw UnsupportedError(
|
||||
'ServiceList only knows how to find the id from a Map object. Provide a custom `Equality` in your call to the constructor.');
|
||||
}
|
||||
});
|
||||
// Index
|
||||
_subs.add(service.onIndexed.where(_notNull).listen((data) {
|
||||
|
@ -310,15 +313,17 @@ class ServiceList<Id, Data> extends DelegatingList<Data> {
|
|||
}));
|
||||
|
||||
// Modified/Updated
|
||||
handleModified(Data item) {
|
||||
void handleModified(Data item) {
|
||||
var indices = <int>[];
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (_equality.equals(item, this[i])) indices.add(i);
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (_equality!.equals(item, this[i])) indices.add(i);
|
||||
}
|
||||
|
||||
if (indices.isNotEmpty) {
|
||||
for (var i in indices) this[i] = item;
|
||||
for (var i in indices) {
|
||||
this[i] = item;
|
||||
}
|
||||
|
||||
_onChange.add(this);
|
||||
}
|
||||
|
@ -331,7 +336,7 @@ class ServiceList<Id, Data> extends DelegatingList<Data> {
|
|||
|
||||
// Removed
|
||||
_subs.add(service.onRemoved.where(_notNull).listen((item) {
|
||||
removeWhere((x) => _equality.equals(item, x));
|
||||
removeWhere((x) => _equality!.equals(item, x));
|
||||
_onChange.add(this);
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ const Map<String, String> _writeHeaders = {
|
|||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
Map<String, String> _buildQuery(Map<String, dynamic> params) {
|
||||
Map<String, String>? _buildQuery(Map<String, dynamic>? params) {
|
||||
return params?.map((k, v) => MapEntry(k, v.toString()));
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ bool _invalid(http.Response response) =>
|
|||
response.statusCode >= 300;
|
||||
|
||||
AngelHttpException failure(http.Response response,
|
||||
{error, String message, StackTrace stack}) {
|
||||
{error, String? message, StackTrace? stack}) {
|
||||
try {
|
||||
var v = json.decode(response.body);
|
||||
|
||||
|
@ -52,7 +52,7 @@ abstract class BaseAngelClient extends Angel {
|
|||
final StreamController<AngelAuthResult> _onAuthenticated =
|
||||
StreamController<AngelAuthResult>();
|
||||
final List<Service> _services = [];
|
||||
final http.BaseClient client;
|
||||
final http.BaseClient? client;
|
||||
|
||||
@override
|
||||
Stream<AngelAuthResult> get onAuthenticated => _onAuthenticated.stream;
|
||||
|
@ -61,7 +61,7 @@ abstract class BaseAngelClient extends Angel {
|
|||
|
||||
@override
|
||||
Future<AngelAuthResult> authenticate(
|
||||
{String type,
|
||||
{String? type,
|
||||
credentials,
|
||||
String authEndpoint = '/auth',
|
||||
@deprecated String reviveEndpoint = '/auth/token'}) async {
|
||||
|
@ -92,14 +92,12 @@ abstract class BaseAngelClient extends Angel {
|
|||
//var v = json.decode(response.body);
|
||||
var v = jsonDecode(response.body);
|
||||
|
||||
if (v is! Map ||
|
||||
!(v as Map).containsKey('data') ||
|
||||
!(v as Map).containsKey('token')) {
|
||||
if (v is! Map || !v.containsKey('data') || !v.containsKey('token')) {
|
||||
throw AngelHttpException.notAuthenticated(
|
||||
message: "Auth endpoint '$url' did not return a proper response.");
|
||||
}
|
||||
|
||||
var r = AngelAuthResult.fromMap(v as Map);
|
||||
var r = AngelAuthResult.fromMap(v);
|
||||
_onAuthenticated.add(r);
|
||||
return r;
|
||||
} on AngelHttpException {
|
||||
|
@ -111,7 +109,7 @@ abstract class BaseAngelClient extends Angel {
|
|||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
client.close();
|
||||
client!.close();
|
||||
await _onAuthenticated.close();
|
||||
await Future.wait(_services.map((s) => s.close())).then((_) {
|
||||
_services.clear();
|
||||
|
@ -128,13 +126,13 @@ abstract class BaseAngelClient extends Angel {
|
|||
if (authToken?.isNotEmpty == true) {
|
||||
request.headers['authorization'] ??= 'Bearer $authToken';
|
||||
}
|
||||
return client.send(request);
|
||||
return client!.send(request);
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
String method, url, Map<String, String>? headers,
|
||||
[body, Encoding? encoding]) async {
|
||||
var request =
|
||||
http.Request(method, url is Uri ? url : Uri.parse(url.toString()));
|
||||
|
||||
|
@ -160,12 +158,12 @@ abstract class BaseAngelClient extends Angel {
|
|||
|
||||
@override
|
||||
Service<Id, Data> service<Id, Data>(String path,
|
||||
{Type type, AngelDeserializer<Data> deserializer}) {
|
||||
{Type? type, AngelDeserializer<Data>? deserializer}) {
|
||||
var url = baseUrl.replace(path: p.join(baseUrl.path, path));
|
||||
var s = BaseAngelService<Id, Data>(client, this, url,
|
||||
deserializer: deserializer);
|
||||
_services.add(s);
|
||||
return s;
|
||||
return s as Service<Id, Data>;
|
||||
}
|
||||
|
||||
Uri _join(url) {
|
||||
|
@ -180,65 +178,65 @@ abstract class BaseAngelClient extends Angel {
|
|||
//}
|
||||
|
||||
@override
|
||||
Future<http.Response> get(url, {Map<String, String> headers}) async {
|
||||
Future<http.Response> get(url, {Map<String, String>? headers}) async {
|
||||
return sendUnstreamed('GET', _join(url), headers);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<http.Response> head(url, {Map<String, String> headers}) async {
|
||||
Future<http.Response> head(url, {Map<String, String>? headers}) async {
|
||||
return sendUnstreamed('HEAD', _join(url), headers);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<http.Response> patch(url,
|
||||
{body, Map<String, String> headers, Encoding encoding}) async {
|
||||
{body, Map<String, String>? headers, Encoding? encoding}) async {
|
||||
return sendUnstreamed('PATCH', _join(url), headers, body, encoding);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<http.Response> post(url,
|
||||
{body, Map<String, String> headers, Encoding encoding}) async {
|
||||
{body, Map<String, String>? headers, Encoding? encoding}) async {
|
||||
return sendUnstreamed('POST', _join(url), headers, body, encoding);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<http.Response> put(url,
|
||||
{body, Map<String, String> headers, Encoding encoding}) async {
|
||||
{body, Map<String, String>? headers, Encoding? encoding}) async {
|
||||
return sendUnstreamed('PUT', _join(url), headers, body, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
class BaseAngelService<Id, Data> extends Service<Id, Data> {
|
||||
class BaseAngelService<Id, Data> extends Service<Id, Data?> {
|
||||
@override
|
||||
final BaseAngelClient app;
|
||||
final Uri baseUrl;
|
||||
final http.BaseClient client;
|
||||
final AngelDeserializer<Data> deserializer;
|
||||
final http.BaseClient? client;
|
||||
final AngelDeserializer<Data>? deserializer;
|
||||
|
||||
final StreamController<List<Data>> _onIndexed = StreamController();
|
||||
final StreamController<Data> _onRead = StreamController(),
|
||||
final StreamController<List<Data?>> _onIndexed = StreamController();
|
||||
final StreamController<Data?> _onRead = StreamController(),
|
||||
_onCreated = StreamController(),
|
||||
_onModified = StreamController(),
|
||||
_onUpdated = StreamController(),
|
||||
_onRemoved = StreamController();
|
||||
|
||||
@override
|
||||
Stream<List<Data>> get onIndexed => _onIndexed.stream;
|
||||
Stream<List<Data?>> get onIndexed => _onIndexed.stream;
|
||||
|
||||
@override
|
||||
Stream<Data> get onRead => _onRead.stream;
|
||||
Stream<Data?> get onRead => _onRead.stream;
|
||||
|
||||
@override
|
||||
Stream<Data> get onCreated => _onCreated.stream;
|
||||
Stream<Data?> get onCreated => _onCreated.stream;
|
||||
|
||||
@override
|
||||
Stream<Data> get onModified => _onModified.stream;
|
||||
Stream<Data?> get onModified => _onModified.stream;
|
||||
|
||||
@override
|
||||
Stream<Data> get onUpdated => _onUpdated.stream;
|
||||
Stream<Data?> get onUpdated => _onUpdated.stream;
|
||||
|
||||
@override
|
||||
Stream<Data> get onRemoved => _onRemoved.stream;
|
||||
Stream<Data?> get onRemoved => _onRemoved.stream;
|
||||
|
||||
@override
|
||||
Future close() async {
|
||||
|
@ -257,8 +255,8 @@ class BaseAngelService<Id, Data> extends Service<Id, Data> {
|
|||
@deprecated
|
||||
String get basePath => baseUrl.toString();
|
||||
|
||||
Data deserialize(x) {
|
||||
return deserializer != null ? deserializer(x) : x as Data;
|
||||
Data? deserialize(x) {
|
||||
return deserializer != null ? deserializer!(x) : x as Data?;
|
||||
}
|
||||
|
||||
String makeBody(x) {
|
||||
|
@ -267,15 +265,15 @@ class BaseAngelService<Id, Data> extends Service<Id, Data> {
|
|||
}
|
||||
|
||||
Future<http.StreamedResponse> send(http.BaseRequest request) {
|
||||
if (app.authToken != null && app.authToken.isNotEmpty) {
|
||||
if (app.authToken != null && app.authToken!.isNotEmpty) {
|
||||
request.headers['Authorization'] = 'Bearer ${app.authToken}';
|
||||
}
|
||||
|
||||
return client.send(request);
|
||||
return client!.send(request);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Data>> index([Map<String, dynamic> params]) async {
|
||||
Future<List<Data?>?> index([Map<String, dynamic>? params]) async {
|
||||
var url = baseUrl.replace(queryParameters: _buildQuery(params));
|
||||
var response = await app.sendUnstreamed('GET', url, _readHeaders);
|
||||
|
||||
|
@ -304,7 +302,7 @@ class BaseAngelService<Id, Data> extends Service<Id, Data> {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<Data> read(id, [Map<String, dynamic> params]) async {
|
||||
Future<Data?> read(id, [Map<String, dynamic>? params]) async {
|
||||
var url = baseUrl.replace(
|
||||
path: p.join(baseUrl.path, id.toString()),
|
||||
queryParameters: _buildQuery(params));
|
||||
|
@ -313,54 +311,58 @@ class BaseAngelService<Id, Data> extends Service<Id, Data> {
|
|||
|
||||
try {
|
||||
if (_invalid(response)) {
|
||||
if (_onRead.hasListener)
|
||||
if (_onRead.hasListener) {
|
||||
_onRead.addError(failure(response));
|
||||
else
|
||||
} else {
|
||||
throw failure(response);
|
||||
}
|
||||
}
|
||||
|
||||
var r = deserialize(json.decode(response.body));
|
||||
_onRead.add(r);
|
||||
return r;
|
||||
} catch (e, st) {
|
||||
if (_onRead.hasListener)
|
||||
if (_onRead.hasListener) {
|
||||
_onRead.addError(e, st);
|
||||
else
|
||||
} else {
|
||||
throw failure(response, error: e, stack: st);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Data> create(data, [Map<String, dynamic> params]) async {
|
||||
Future<Data?> create(data, [Map<String, dynamic>? params]) async {
|
||||
var url = baseUrl.replace(queryParameters: _buildQuery(params));
|
||||
var response =
|
||||
await app.sendUnstreamed('POST', url, _writeHeaders, makeBody(data));
|
||||
|
||||
try {
|
||||
if (_invalid(response)) {
|
||||
if (_onCreated.hasListener)
|
||||
if (_onCreated.hasListener) {
|
||||
_onCreated.addError(failure(response));
|
||||
else
|
||||
} else {
|
||||
throw failure(response);
|
||||
}
|
||||
}
|
||||
|
||||
var r = deserialize(json.decode(response.body));
|
||||
_onCreated.add(r);
|
||||
return r;
|
||||
} catch (e, st) {
|
||||
if (_onCreated.hasListener)
|
||||
if (_onCreated.hasListener) {
|
||||
_onCreated.addError(e, st);
|
||||
else
|
||||
} else {
|
||||
throw failure(response, error: e, stack: st);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Data> modify(id, data, [Map<String, dynamic> params]) async {
|
||||
Future<Data?> modify(id, data, [Map<String, dynamic>? params]) async {
|
||||
var url = baseUrl.replace(
|
||||
path: p.join(baseUrl.path, id.toString()),
|
||||
queryParameters: _buildQuery(params));
|
||||
|
@ -370,27 +372,29 @@ class BaseAngelService<Id, Data> extends Service<Id, Data> {
|
|||
|
||||
try {
|
||||
if (_invalid(response)) {
|
||||
if (_onModified.hasListener)
|
||||
if (_onModified.hasListener) {
|
||||
_onModified.addError(failure(response));
|
||||
else
|
||||
} else {
|
||||
throw failure(response);
|
||||
}
|
||||
}
|
||||
|
||||
var r = deserialize(json.decode(response.body));
|
||||
_onModified.add(r);
|
||||
return r;
|
||||
} catch (e, st) {
|
||||
if (_onModified.hasListener)
|
||||
if (_onModified.hasListener) {
|
||||
_onModified.addError(e, st);
|
||||
else
|
||||
} else {
|
||||
throw failure(response, error: e, stack: st);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Data> update(id, data, [Map<String, dynamic> params]) async {
|
||||
Future<Data?> update(id, data, [Map<String, dynamic>? params]) async {
|
||||
var url = baseUrl.replace(
|
||||
path: p.join(baseUrl.path, id.toString()),
|
||||
queryParameters: _buildQuery(params));
|
||||
|
@ -400,27 +404,29 @@ class BaseAngelService<Id, Data> extends Service<Id, Data> {
|
|||
|
||||
try {
|
||||
if (_invalid(response)) {
|
||||
if (_onUpdated.hasListener)
|
||||
if (_onUpdated.hasListener) {
|
||||
_onUpdated.addError(failure(response));
|
||||
else
|
||||
} else {
|
||||
throw failure(response);
|
||||
}
|
||||
}
|
||||
|
||||
var r = deserialize(json.decode(response.body));
|
||||
_onUpdated.add(r);
|
||||
return r;
|
||||
} catch (e, st) {
|
||||
if (_onUpdated.hasListener)
|
||||
if (_onUpdated.hasListener) {
|
||||
_onUpdated.addError(e, st);
|
||||
else
|
||||
} else {
|
||||
throw failure(response, error: e, stack: st);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Data> remove(id, [Map<String, dynamic> params]) async {
|
||||
Future<Data?> remove(id, [Map<String, dynamic>? params]) async {
|
||||
var url = baseUrl.replace(
|
||||
path: p.join(baseUrl.path, id.toString()),
|
||||
queryParameters: _buildQuery(params));
|
||||
|
@ -429,21 +435,23 @@ class BaseAngelService<Id, Data> extends Service<Id, Data> {
|
|||
|
||||
try {
|
||||
if (_invalid(response)) {
|
||||
if (_onRemoved.hasListener)
|
||||
if (_onRemoved.hasListener) {
|
||||
_onRemoved.addError(failure(response));
|
||||
else
|
||||
} else {
|
||||
throw failure(response);
|
||||
}
|
||||
}
|
||||
|
||||
var r = deserialize(json.decode(response.body));
|
||||
_onRemoved.add(r);
|
||||
return r;
|
||||
} catch (e, st) {
|
||||
if (_onRemoved.hasListener)
|
||||
if (_onRemoved.hasListener) {
|
||||
_onRemoved.addError(e, st);
|
||||
else
|
||||
} else {
|
||||
throw failure(response, error: e, stack: st);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -13,20 +13,21 @@ export 'angel_client.dart';
|
|||
|
||||
/// Queries an Angel server via REST.
|
||||
class Rest extends BaseAngelClient {
|
||||
Rest(String basePath) : super(new http.BrowserClient(), basePath);
|
||||
Rest(String basePath) : super(http.BrowserClient(), basePath);
|
||||
|
||||
@override
|
||||
Future<AngelAuthResult> authenticate(
|
||||
{String type,
|
||||
{String? type,
|
||||
credentials,
|
||||
String authEndpoint = '/auth',
|
||||
@deprecated String reviveEndpoint = '/auth/token'}) async {
|
||||
if (type == null || type == 'token') {
|
||||
if (!window.localStorage.containsKey('token')) {
|
||||
throw new Exception(
|
||||
throw Exception(
|
||||
'Cannot revive token from localStorage - there is none.');
|
||||
}
|
||||
|
||||
var token = json.decode(window.localStorage['token']);
|
||||
var token = json.decode(window.localStorage['token']!);
|
||||
credentials ??= {'token': token};
|
||||
}
|
||||
|
||||
|
@ -39,33 +40,34 @@ class Rest extends BaseAngelClient {
|
|||
|
||||
@override
|
||||
Stream<String> authenticateViaPopup(String url,
|
||||
{String eventName = 'token', String errorMessage}) {
|
||||
var ctrl = new StreamController<String>();
|
||||
{String eventName = 'token', String? errorMessage}) {
|
||||
var ctrl = StreamController<String>();
|
||||
var wnd = window.open(url, 'angel_client_auth_popup');
|
||||
|
||||
Timer t;
|
||||
StreamSubscription sub;
|
||||
t = new Timer.periodic(new Duration(milliseconds: 500), (timer) {
|
||||
StreamSubscription? sub;
|
||||
t = Timer.periodic(Duration(milliseconds: 500), (timer) {
|
||||
if (!ctrl.isClosed) {
|
||||
if (wnd.closed) {
|
||||
ctrl.addError(new AngelHttpException.notAuthenticated(
|
||||
if (wnd.closed!) {
|
||||
ctrl.addError(AngelHttpException.notAuthenticated(
|
||||
message:
|
||||
errorMessage ?? 'Authentication via popup window failed.'));
|
||||
ctrl.close();
|
||||
timer.cancel();
|
||||
sub?.cancel();
|
||||
}
|
||||
} else
|
||||
} else {
|
||||
timer.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
sub = window.on[eventName ?? 'token'].listen((Event ev) {
|
||||
sub = window.on[eventName].listen((Event ev) {
|
||||
var e = ev as CustomEvent;
|
||||
if (!ctrl.isClosed) {
|
||||
ctrl.add(e.detail.toString());
|
||||
t.cancel();
|
||||
ctrl.close();
|
||||
sub.cancel();
|
||||
sub!.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -8,12 +8,12 @@ export 'angel_client.dart';
|
|||
|
||||
/// Queries an Angel server via REST.
|
||||
class Rest extends BaseAngelClient {
|
||||
Rest(String basePath) : super(new http.Client() as http.BaseClient, basePath);
|
||||
Rest(String basePath) : super(http.Client() as http.BaseClient, basePath);
|
||||
|
||||
@override
|
||||
Stream<String> authenticateViaPopup(String url,
|
||||
{String eventName = 'token'}) {
|
||||
throw new UnimplementedError(
|
||||
throw UnimplementedError(
|
||||
'Opening popup windows is not supported in the `flutter` client.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,11 +17,11 @@ class Rest extends BaseAngelClient {
|
|||
|
||||
@override
|
||||
Service<Id, Data> service<Id, Data>(String path,
|
||||
{Type type, AngelDeserializer deserializer}) {
|
||||
{Type? type, AngelDeserializer? deserializer}) {
|
||||
var url = baseUrl.replace(path: p.join(baseUrl.path, path));
|
||||
var s = RestService<Id, Data>(client, this, url, type);
|
||||
_services.add(s);
|
||||
return s;
|
||||
return s as Service<Id, Data>;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -42,21 +42,21 @@ class Rest extends BaseAngelClient {
|
|||
|
||||
/// Queries an Angel service via REST.
|
||||
class RestService<Id, Data> extends BaseAngelService<Id, Data> {
|
||||
final Type type;
|
||||
final Type? type;
|
||||
|
||||
RestService(http.BaseClient client, BaseAngelClient app, url, this.type)
|
||||
RestService(http.BaseClient? client, BaseAngelClient app, url, this.type)
|
||||
: super(client, app, url);
|
||||
|
||||
@override
|
||||
Data deserialize(x) {
|
||||
Data? deserialize(x) {
|
||||
print(x);
|
||||
if (type != null) {
|
||||
return x.runtimeType == type
|
||||
? x as Data
|
||||
: god.deserializeDatum(x, outputType: type) as Data;
|
||||
? x as Data?
|
||||
: god.deserializeDatum(x, outputType: type) as Data?;
|
||||
}
|
||||
|
||||
return x as Data;
|
||||
return x as Data?;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -1,41 +1,45 @@
|
|||
name: angel_client
|
||||
version: 3.0.0
|
||||
version: 4.0.0
|
||||
description: Support for querying Angel servers in the browser, Flutter, and command-line.
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/angel_client
|
||||
publish_to: none
|
||||
environment:
|
||||
sdk: ">=2.10.0 <3.0.0"
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
dependencies:
|
||||
angel_http_exception:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/http_exception
|
||||
collection: ^1.0.0
|
||||
http: ^0.13.0
|
||||
collection: ^1.15.0
|
||||
http: ^0.13.1
|
||||
json_god:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/json_god
|
||||
#dart_json_mapper: ^1.7.0
|
||||
meta: ^1.0.0
|
||||
path: ^1.0.0
|
||||
meta: ^1.3.0
|
||||
path: ^1.8.0
|
||||
dev_dependencies:
|
||||
angel_framework:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/framework
|
||||
angel_model:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/model
|
||||
async: ^2.0.0
|
||||
build_runner: ^1.0.0
|
||||
build_web_compilers: ^2.12.2
|
||||
mock_request: ^1.0.0
|
||||
async: ^2.5.0
|
||||
build_runner: ^1.12.2
|
||||
build_web_compilers: ^2.16.5
|
||||
mock_request:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/mock_request
|
||||
pedantic: ^1.11.0
|
||||
test: ^1.16.5
|
||||
test: ^1.16.8
|
||||
|
|
|
@ -9,75 +9,75 @@ void main() {
|
|||
test('sets method,body,headers,path', () async {
|
||||
await app.post(Uri.parse('/post'),
|
||||
headers: {'method': 'post'}, body: 'post');
|
||||
expect(app.client.spec.method, 'POST');
|
||||
expect(app.client.spec.path, '/post');
|
||||
expect(app.client.spec.headers['method'], 'post');
|
||||
expect(await read(app.client.spec.request.finalize()), 'post');
|
||||
expect(app.client.spec!.method, 'POST');
|
||||
expect(app.client.spec!.path, '/post');
|
||||
expect(app.client.spec!.headers['method'], 'post');
|
||||
expect(await read(app.client.spec!.request.finalize()), 'post');
|
||||
});
|
||||
|
||||
group('service methods', () {
|
||||
test('index', () async {
|
||||
await todoService.index();
|
||||
expect(app.client.spec.method, 'GET');
|
||||
expect(app.client.spec.path, '/api/todos');
|
||||
expect(app.client.spec!.method, 'GET');
|
||||
expect(app.client.spec!.path, '/api/todos');
|
||||
});
|
||||
|
||||
test('read', () async {
|
||||
await todoService.read('sleep');
|
||||
expect(app.client.spec.method, 'GET');
|
||||
expect(app.client.spec.path, '/api/todos/sleep');
|
||||
expect(app.client.spec!.method, 'GET');
|
||||
expect(app.client.spec!.path, '/api/todos/sleep');
|
||||
});
|
||||
|
||||
test('create', () async {
|
||||
await todoService.create({});
|
||||
expect(app.client.spec.method, 'POST');
|
||||
expect(app.client.spec.headers['content-type'],
|
||||
expect(app.client.spec!.method, 'POST');
|
||||
expect(app.client.spec!.headers['content-type'],
|
||||
startsWith('application/json'));
|
||||
expect(app.client.spec.path, '/api/todos');
|
||||
expect(await read(app.client.spec.request.finalize()), '{}');
|
||||
expect(app.client.spec!.path, '/api/todos');
|
||||
expect(await read(app.client.spec!.request.finalize()), '{}');
|
||||
});
|
||||
|
||||
test('modify', () async {
|
||||
await todoService.modify('sleep', {});
|
||||
expect(app.client.spec.method, 'PATCH');
|
||||
expect(app.client.spec.headers['content-type'],
|
||||
expect(app.client.spec!.method, 'PATCH');
|
||||
expect(app.client.spec!.headers['content-type'],
|
||||
startsWith('application/json'));
|
||||
expect(app.client.spec.path, '/api/todos/sleep');
|
||||
expect(await read(app.client.spec.request.finalize()), '{}');
|
||||
expect(app.client.spec!.path, '/api/todos/sleep');
|
||||
expect(await read(app.client.spec!.request.finalize()), '{}');
|
||||
});
|
||||
|
||||
test('update', () async {
|
||||
await todoService.update('sleep', {});
|
||||
expect(app.client.spec.method, 'POST');
|
||||
expect(app.client.spec.headers['content-type'],
|
||||
expect(app.client.spec!.method, 'POST');
|
||||
expect(app.client.spec!.headers['content-type'],
|
||||
startsWith('application/json'));
|
||||
expect(app.client.spec.path, '/api/todos/sleep');
|
||||
expect(await read(app.client.spec.request.finalize()), '{}');
|
||||
expect(app.client.spec!.path, '/api/todos/sleep');
|
||||
expect(await read(app.client.spec!.request.finalize()), '{}');
|
||||
});
|
||||
|
||||
test('remove', () async {
|
||||
await todoService.remove('sleep');
|
||||
expect(app.client.spec.method, 'DELETE');
|
||||
expect(app.client.spec.path, '/api/todos/sleep');
|
||||
expect(app.client.spec!.method, 'DELETE');
|
||||
expect(app.client.spec!.path, '/api/todos/sleep');
|
||||
});
|
||||
});
|
||||
|
||||
group('authentication', () {
|
||||
test('no type defaults to token', () async {
|
||||
await app.authenticate(credentials: '<jwt>');
|
||||
expect(app.client.spec.path, '/auth/token');
|
||||
expect(app.client.spec!.path, '/auth/token');
|
||||
});
|
||||
|
||||
test('sets type', () async {
|
||||
await app.authenticate(type: 'local');
|
||||
expect(app.client.spec.path, '/auth/local');
|
||||
expect(app.client.spec!.path, '/auth/local');
|
||||
});
|
||||
|
||||
test('credentials send right body', () async {
|
||||
await app
|
||||
.authenticate(type: 'local', credentials: {'username': 'password'});
|
||||
expect(
|
||||
await read(app.client.spec.request.finalize()),
|
||||
await read(app.client.spec!.request.finalize()),
|
||||
json.encode({'username': 'password'}),
|
||||
);
|
||||
});
|
||||
|
|
|
@ -10,24 +10,25 @@ Future<String> read(Stream<List<int>> stream) =>
|
|||
|
||||
class MockAngel extends BaseAngelClient {
|
||||
@override
|
||||
final SpecClient client = new SpecClient();
|
||||
final SpecClient client = SpecClient();
|
||||
|
||||
MockAngel() : super(null, 'http://localhost:3000');
|
||||
|
||||
@override
|
||||
authenticateViaPopup(String url, {String eventName = 'token'}) {
|
||||
throw new UnsupportedError('Nope');
|
||||
Stream<String> authenticateViaPopup(String url,
|
||||
{String eventName = 'token'}) {
|
||||
throw UnsupportedError('Nope');
|
||||
}
|
||||
}
|
||||
|
||||
class SpecClient extends http.BaseClient {
|
||||
Spec _spec;
|
||||
Spec? _spec;
|
||||
|
||||
Spec get spec => _spec;
|
||||
Spec? get spec => _spec;
|
||||
|
||||
@override
|
||||
send(http.BaseRequest request) {
|
||||
_spec = new Spec(request, request.method, request.url.path, request.headers,
|
||||
Future<http.StreamedResponse> send(http.BaseRequest request) {
|
||||
_spec = Spec(request, request.method, request.url.path, request.headers,
|
||||
request.contentLength);
|
||||
dynamic data = {'text': 'Clean your room!', 'completed': true};
|
||||
|
||||
|
@ -40,8 +41,8 @@ class SpecClient extends http.BaseClient {
|
|||
data = [data];
|
||||
}
|
||||
|
||||
return new Future<http.StreamedResponse>.value(new http.StreamedResponse(
|
||||
new Stream<List<int>>.fromIterable([utf8.encode(json.encode(data))]),
|
||||
return Future<http.StreamedResponse>.value(http.StreamedResponse(
|
||||
Stream<List<int>>.fromIterable([utf8.encode(json.encode(data))]),
|
||||
200,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
|
@ -54,7 +55,7 @@ class Spec {
|
|||
final http.BaseRequest request;
|
||||
final String method, path;
|
||||
final Map<String, String> headers;
|
||||
final int contentLength;
|
||||
final int? contentLength;
|
||||
|
||||
Spec(this.request, this.method, this.path, this.headers, this.contentLength);
|
||||
|
||||
|
|
|
@ -6,22 +6,22 @@ import 'package:angel_framework/http.dart' as s;
|
|||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
HttpServer server;
|
||||
c.Angel app;
|
||||
c.ServiceList list;
|
||||
StreamQueue queue;
|
||||
void main() {
|
||||
late HttpServer server;
|
||||
late c.Angel app;
|
||||
late c.ServiceList list;
|
||||
late StreamQueue queue;
|
||||
|
||||
setUp(() async {
|
||||
var serverApp = new s.Angel();
|
||||
var http = new s.AngelHttp(serverApp);
|
||||
serverApp.use('/api/todos', new s.MapService(autoIdAndDateFields: false));
|
||||
var serverApp = s.Angel();
|
||||
var http = s.AngelHttp(serverApp);
|
||||
serverApp.use('/api/todos', s.MapService(autoIdAndDateFields: false));
|
||||
|
||||
server = await http.startServer();
|
||||
var uri = 'http://${server.address.address}:${server.port}';
|
||||
app = new c.Rest(uri);
|
||||
list = new c.ServiceList(app.service('api/todos'));
|
||||
queue = new StreamQueue(list.onChange);
|
||||
app = c.Rest(uri);
|
||||
list = c.ServiceList(app.service('api/todos'));
|
||||
queue = StreamQueue(list.onChange);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import 'package:angel_model/angel_model.dart';
|
||||
|
||||
class Postcard extends Model {
|
||||
String location;
|
||||
String message;
|
||||
String? location;
|
||||
String? message;
|
||||
|
||||
Postcard({String id, this.location, this.message}) {
|
||||
Postcard({String? id, this.location, this.message}) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
factory Postcard.fromJson(Map data) => new Postcard(
|
||||
factory Postcard.fromJson(Map data) => Postcard(
|
||||
id: data['id'].toString(),
|
||||
location: data['location'].toString(),
|
||||
message: data['message'].toString());
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:html';
|
|||
import 'package:angel_client/browser.dart';
|
||||
|
||||
/// Dummy app to ensure client works with DDC.
|
||||
main() {
|
||||
var app = new Rest(window.location.origin);
|
||||
void main() {
|
||||
var app = Rest(window.location.origin);
|
||||
window.alert(app.baseUrl.toString());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue