Migrated angel_client to 4.0.0

This commit is contained in:
thomashii 2021-04-10 21:22:20 +08:00
parent 5e9172aca9
commit 037e82d699
13 changed files with 220 additions and 200 deletions

View file

@ -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)

View file

@ -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};
}

View file

@ -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);
}));
}

View file

@ -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,20 +435,22 @@ 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;

View file

@ -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();
}
});

View file

@ -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.');
}
}

View file

@ -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

View file

@ -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

View file

@ -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'}),
);
});

View file

@ -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);

View file

@ -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 {

View file

@ -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());

View file

@ -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());
}