From a95dd008c91fe2bd2bbc8b999e7f8327708fb7b6 Mon Sep 17 00:00:00 2001 From: Chima Precious Date: Sat, 27 Jan 2024 20:15:39 +0000 Subject: [PATCH 1/7] pass stack trace along with error --- packages/pharaoh/lib/src/core_impl.dart | 31 ++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/pharaoh/lib/src/core_impl.dart b/packages/pharaoh/lib/src/core_impl.dart index e0d598b0..2b2f2c3f 100644 --- a/packages/pharaoh/lib/src/core_impl.dart +++ b/packages/pharaoh/lib/src/core_impl.dart @@ -1,6 +1,8 @@ part of 'core.dart'; -class $PharaohImpl extends RouterContract with RouteDefinitionMixin implements Pharaoh { +class $PharaohImpl extends RouterContract + with RouteDefinitionMixin + implements Pharaoh { late final HttpServer _server; OnErrorCallback? _onErrorCb; @@ -60,11 +62,12 @@ class $PharaohImpl extends RouterContract with RouteDefinitionMixin implements P @override Future listen({int port = 3000}) async { - _server = await HttpServer.bind('0.0.0.0', port, shared: true) + _server = await HttpServer.bind(InternetAddress.anyIPv4, port, shared: true) ..autoCompress = true; _server.listen(handleRequest); - print('Server start on PORT: ${_server.port} -> ${uri.scheme}://localhost:${_server.port}'); + print( + 'Server start on PORT: ${_server.port} -> ${uri.scheme}://localhost:${_server.port}'); return this; } @@ -79,17 +82,18 @@ class $PharaohImpl extends RouterContract with RouteDefinitionMixin implements P final request = Request.from(httpReq); final response = Response.create(); - late Object requestError; + late ({Object error, StackTrace trace}) requestError; + try { final result = await resolveAndExecuteHandlers(request, response); return forward(httpReq, result.res); - } catch (error) { - requestError = error; + } catch (error, trace) { + requestError = (error: error, trace: trace); } if (_onErrorCb == null) { var errorResponse = response.internalServerError(requestError.toString()); - if (requestError is SpannerRouteValidatorError) { + if (requestError.error is SpannerRouteValidatorError) { errorResponse = response.status(HttpStatus.unprocessableEntity); } return forward(httpReq, errorResponse); @@ -106,7 +110,9 @@ class $PharaohImpl extends RouterContract with RouteDefinitionMixin implements P final routeResult = spanner.lookup(req.method, req.path); final resolvedHandlers = routeResult?.values.cast() ?? []; - if (routeResult == null || resolvedHandlers.isEmpty) return reqRes.merge(routeNotFound()); + if (routeResult == null || resolvedHandlers.isEmpty) { + return reqRes.merge(routeNotFound()); + } /// update request params with params resolved from spanner for (final param in routeResult.params.entries) { @@ -147,7 +153,8 @@ class $PharaohImpl extends RouterContract with RouteDefinitionMixin implements P res_.mimeType != 'multipart/byteranges') { // If the response isn't chunked yet and there's no other way to tell its // length, enable `dart:io`'s chunked encoding. - request.response.headers.set(HttpHeaders.transferEncodingHeader, 'chunked'); + request.response.headers + .set(HttpHeaders.transferEncodingHeader, 'chunked'); } // headers to write to the response @@ -159,12 +166,14 @@ class $PharaohImpl extends RouterContract with RouteDefinitionMixin implements P request.response.headers.add(_XPoweredByHeader, 'Pharaoh'); } if (!hders.containsKey(HttpHeaders.dateHeader)) { - request.response.headers.add(HttpHeaders.dateHeader, DateTime.now().toUtc()); + request.response.headers + .add(HttpHeaders.dateHeader, DateTime.now().toUtc()); } if (!hders.containsKey(HttpHeaders.contentLengthHeader)) { final contentLength = res_.contentLength; if (contentLength != null) { - request.response.headers.add(HttpHeaders.contentLengthHeader, contentLength); + request.response.headers + .add(HttpHeaders.contentLengthHeader, contentLength); } } From 5b2d477ab34ebc4986c0cc772b2e9ab515879855 Mon Sep 17 00:00:00 2001 From: Chima Precious Date: Sat, 27 Jan 2024 20:32:21 +0000 Subject: [PATCH 2/7] fix failing tests --- packages/pharaoh/lib/src/core_impl.dart | 15 ++-- .../acceptance/request_handling_test.dart | 77 +++++++++++++++---- packages/pharaoh/test/core_test.dart | 12 ++- .../pharaoh/test/http/res.render_test.dart | 12 ++- .../test/middleware/session_mw_test.dart | 41 +++++++--- 5 files changed, 121 insertions(+), 36 deletions(-) diff --git a/packages/pharaoh/lib/src/core_impl.dart b/packages/pharaoh/lib/src/core_impl.dart index 2b2f2c3f..48c4b8fc 100644 --- a/packages/pharaoh/lib/src/core_impl.dart +++ b/packages/pharaoh/lib/src/core_impl.dart @@ -92,11 +92,16 @@ class $PharaohImpl extends RouterContract } if (_onErrorCb == null) { - var errorResponse = response.internalServerError(requestError.toString()); - if (requestError.error is SpannerRouteValidatorError) { - errorResponse = response.status(HttpStatus.unprocessableEntity); - } - return forward(httpReq, errorResponse); + final status = requestError.error is SpannerRouteValidatorError + ? HttpStatus.unprocessableEntity + : HttpStatus.internalServerError; + return forward( + httpReq, + response.json({ + 'error': requestError.error.toString(), + 'trace': requestError.trace.toString() + }, statusCode: status), + ); } final result = await _onErrorCb!.call(requestError, request, response); diff --git a/packages/pharaoh/test/acceptance/request_handling_test.dart b/packages/pharaoh/test/acceptance/request_handling_test.dart index 23db3826..b34ae28a 100644 --- a/packages/pharaoh/test/acceptance/request_handling_test.dart +++ b/packages/pharaoh/test/acceptance/request_handling_test.dart @@ -11,11 +11,22 @@ void main() { ..post('/home/strange', (req, res) => res.ok('Post something 🚀')) ..get('/chima/', (req, res) => res.ok('Foo Bar')); - await (await request(app)).get('/home/chima').expectStatus(200).expectBody('Okay 🚀').test(); + await (await request(app)) + .get('/home/chima') + .expectStatus(200) + .expectBody('Okay 🚀') + .test(); - await (await request(app)).post('/home/strange', {}).expectStatus(200).expectBody('Post something 🚀').test(); + await (await request(app)) + .post('/home/strange', {}) + .expectStatus(200) + .expectBody('Post something 🚀') + .test(); - await (await request(app)).get('/users/204').expectStatus(200).expectBody({'userId': '204'}).test(); + await (await request(app)) + .get('/users/204') + .expectStatus(200) + .expectBody({'userId': '204'}).test(); await (await request(app)) .post('/users/204398938948374797', {}) @@ -26,31 +37,49 @@ void main() { await (await request(app)) .get('/something-new-is-here') .expectStatus(404) - .expectBody({"error": "Route not found: /something-new-is-here"}).test(); + .expectBody( + {"error": "Route not found: /something-new-is-here"}).test(); - await (await request(app)).delete('/home/chima').expectStatus(200).expectBody('Item deleted').test(); + await (await request(app)) + .delete('/home/chima') + .expectStatus(200) + .expectBody('Item deleted') + .test(); await (await request(app)) .get('/chima/asbc') .expectStatus(422) - .expectBody({'error': 'Invalid argument: Invalid parameter value: \"asbc\"'}).test(); + .expectJsonBody(containsPair( + 'error', + 'Invalid argument: Invalid parameter value: \"asbc\"', + )) + .test(); }); group('execute middleware and request', () { test('on base path /', () async { final app = Pharaoh() ..use((req, res, next) => next(req..setParams('foo', 'bar'))) - ..get('/', (req, res) => res.json({...req.params, "name": 'Hello World'})); + ..get('/', + (req, res) => res.json({...req.params, "name": 'Hello World'})); - await (await request(app)).get('/').expectStatus(200).expectBody({'foo': 'bar', 'name': 'Hello World'}).test(); + await (await request(app)) + .get('/') + .expectStatus(200) + .expectBody({'foo': 'bar', 'name': 'Hello World'}).test(); }); test('of level 1', () async { final app = Pharaoh() ..use((req, res, next) => next(req..setParams('name', 'Chima'))) - ..get('/foo/bar', (req, res) => res.ok('Name: ${req.params['name']} 🚀')); + ..get( + '/foo/bar', (req, res) => res.ok('Name: ${req.params['name']} 🚀')); - await (await request(app)).get('/foo/bar').expectStatus(200).expectBody('Name: Chima 🚀').test(); + await (await request(app)) + .get('/foo/bar') + .expectStatus(200) + .expectBody('Name: Chima 🚀') + .test(); }); test('of level 2', () async { @@ -59,7 +88,10 @@ void main() { ..use((req, res, next) => next(req..setParams('age', '14'))) ..get('/foo/bar', (req, res) => res.json(req.params)); - await (await request(app)).get('/foo/bar').expectStatus(200).expectBody({'name': 'Chima', 'age': '14'}).test(); + await (await request(app)) + .get('/foo/bar') + .expectStatus(200) + .expectBody({'name': 'Chima', 'age': '14'}).test(); }); test('of level 3', () async { @@ -94,7 +126,11 @@ void main() { ..use((req, res, next) => next(res.ok('Say hello'))) ..get('/foo/bar', (req, res) => res.json(req.params)); - await (await request(app)).get('/foo/bar').expectStatus(200).expectBody('Say hello').test(); + await (await request(app)) + .get('/foo/bar') + .expectStatus(200) + .expectBody('Say hello') + .test(); }); test('should execute route groups', () async { @@ -110,11 +146,22 @@ void main() { app.group('/api/v1', router); - await (await request(app)).get('/users/chima').expectStatus(200).expectBody({'userId': 'chima'}).test(); + await (await request(app)) + .get('/users/chima') + .expectStatus(200) + .expectBody({'userId': 'chima'}).test(); - await (await request(app)).get('/api/v1').expectStatus(200).expectBody('Group working').test(); + await (await request(app)) + .get('/api/v1') + .expectStatus(200) + .expectBody('Group working') + .test(); - await (await request(app)).delete('/api/v1/say-hello').expectStatus(200).expectBody('Hello World').test(); + await (await request(app)) + .delete('/api/v1/say-hello') + .expectStatus(200) + .expectBody('Hello World') + .test(); }); }); } diff --git a/packages/pharaoh/test/core_test.dart b/packages/pharaoh/test/core_test.dart index e70fffec..01cce59b 100644 --- a/packages/pharaoh/test/core_test.dart +++ b/packages/pharaoh/test/core_test.dart @@ -4,18 +4,24 @@ import 'package:spookie/spookie.dart'; void main() { group('pharaoh_core', () { test('should initialize without onError callback', () async { - final app = Pharaoh()..get('/', (req, res) => throw ArgumentError('Some weird error')); + final app = Pharaoh() + ..get('/', (req, res) => throw ArgumentError('Some weird error')); await (await request(app)) .get('/') .expectStatus(500) - .expectBody({'error': 'Invalid argument(s): Some weird error'}).test(); + .expectJsonBody(allOf( + containsPair('error', 'Invalid argument(s): Some weird error'), + contains('trace'), + )) + .test(); }); test('should use onError callback if provided', () async { final app = Pharaoh() ..use((req, res, next) => next(res.header('foo', 'bar'))) - ..onError((error, req, res) => res.status(500).withBody('An error occurred just now')) + ..onError((_, req, res) => + res.status(500).withBody('An error occurred just now')) ..get('/', (req, res) => throw ArgumentError('Some weird error')); await (await request(app)) diff --git a/packages/pharaoh/test/http/res.render_test.dart b/packages/pharaoh/test/http/res.render_test.dart index d2f18fd9..85fb8af1 100644 --- a/packages/pharaoh/test/http/res.render_test.dart +++ b/packages/pharaoh/test/http/res.render_test.dart @@ -27,7 +27,11 @@ void main() { }); test('should render template with variables', () async { - app = app..get('/', (req, res) => res.render('welcome', {'username': 'Spookie'})); + app = app + ..get( + '/', + (req, res) => res.render('welcome', {'username': 'Spookie'}), + ); await (await request(app)) .get('/') @@ -55,8 +59,10 @@ void main() { await (await request(app)) .get('/') .expectStatus(500) - .expectContentType('application/json; charset=utf-8') - .expectBody({'error': 'Pharaoh Error(s): No view engine found'}).test(); + .expectJsonBody( + containsPair('error', 'Pharaoh Error(s): No view engine found'), + ) + .test(); }); }); } diff --git a/packages/pharaoh/test/middleware/session_mw_test.dart b/packages/pharaoh/test/middleware/session_mw_test.dart index b31cdf1c..5afa73f8 100644 --- a/packages/pharaoh/test/middleware/session_mw_test.dart +++ b/packages/pharaoh/test/middleware/session_mw_test.dart @@ -94,7 +94,8 @@ void main() { test('should do nothing if req.session exists', () async { final app = Pharaoh() ..use((req, res, next) { - final session = Session('id')..cookie = bakeCookie('phar', 'adf', CookieOpts()); + final session = Session('id') + ..cookie = bakeCookie('phar', 'adf', CookieOpts()); req[RequestContext.session] = session; next((req)); @@ -149,7 +150,8 @@ void main() { test('should pass session fetch error', () async { const opts = CookieOpts(secret: 'foo bar baz'); - final store = _$TestStore(getFunc: (_) => throw Exception('Session store not available')); + final store = _$TestStore( + getFunc: (_) => throw Exception('Session store not available')); final app = Pharaoh() ..use(cookieParser(opts: opts)) @@ -162,7 +164,10 @@ void main() { 'pharaoh.sid=s%3A4badf56b-ab39-4d77-8992-934c995772da.vqiT1VnWppTRhR2pr4F4vb9Oxrbn67E0n0txjKD0qJ4; Path=/' }) .expectStatus(500) - .expectBody({"error": "Exception: Session store not available"}) + .expectJsonBody(containsPair( + 'error', + "Exception: Session store not available", + )) .test(); }); @@ -213,7 +218,8 @@ void main() { test('should prevent save of uninitialized session', () async { final store = InMemoryStore(); final app = Pharaoh() - ..use(sessionMdw(secret: 'foo bar fuz', store: store, saveUninitialized: false)) + ..use(sessionMdw( + secret: 'foo bar fuz', store: store, saveUninitialized: false)) ..get('/', (req, res) => res.end()); await (await request(app)) @@ -228,7 +234,8 @@ void main() { test('should still save modified session', () async { final store = InMemoryStore(); final app = Pharaoh() - ..use(sessionMdw(secret: 'foo bar fuz', store: store, saveUninitialized: false)) + ..use(sessionMdw( + secret: 'foo bar fuz', store: store, saveUninitialized: false)) ..get('/', (req, res) { req.session?['name'] = 'Chima'; req.session?['world'] = 'World'; @@ -250,7 +257,8 @@ void main() { setFunc: (_, __) => throw Exception('Boom shakalaka'), ); final app = Pharaoh() - ..use(sessionMdw(secret: 'foo bar fuz', store: store, saveUninitialized: false)) + ..use(sessionMdw( + secret: 'foo bar fuz', store: store, saveUninitialized: false)) ..get('/', (req, res) { req.session?['name'] = 'Chima'; req.session?['world'] = 'World'; @@ -261,7 +269,8 @@ void main() { await (await request(app)) .get('/') .expectStatus(500) - .expectBody({"error": "Exception: Boom shakalaka"}).test(); + .expectJsonBody(containsPair('error', "Exception: Boom shakalaka")) + .test(); }); }); @@ -275,7 +284,11 @@ void main() { final app = Pharaoh() ..use(cookieParser(opts: opts)) - ..use(sessionMdw(cookie: opts, store: store, rolling: true, saveUninitialized: false)) + ..use(sessionMdw( + cookie: opts, + store: store, + rolling: true, + saveUninitialized: false)) ..get('/', (req, res) => res.end()); await (await request(app)) @@ -297,7 +310,11 @@ void main() { final app = Pharaoh() ..use(cookieParser(opts: opts)) - ..use(sessionMdw(cookie: opts, store: store, rolling: true, saveUninitialized: true)) + ..use(sessionMdw( + cookie: opts, + store: store, + rolling: true, + saveUninitialized: true)) ..get('/', (req, res) => res.end()); await (await request(app)) @@ -319,7 +336,11 @@ void main() { final app = Pharaoh() ..use(cookieParser(opts: opts)) - ..use(sessionMdw(cookie: opts, store: store, rolling: true, saveUninitialized: false)) + ..use(sessionMdw( + cookie: opts, + store: store, + rolling: true, + saveUninitialized: false)) ..get('/', (req, res) { req.session?['name'] = 'codekeyz'; return res.end(); From 61119a3f2a9ca583217bd5206ae3e8e23bfdee8d Mon Sep 17 00:00:00 2001 From: Chima Precious Date: Sun, 28 Jan 2024 12:24:56 +0000 Subject: [PATCH 3/7] fix passing unknown route as parametric --- packages/spanner/lib/src/tree/tree.dart | 51 +++++++++++++++++------- packages/spanner/test/wildcard_test.dart | 24 ++++++++++- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/packages/spanner/lib/src/tree/tree.dart b/packages/spanner/lib/src/tree/tree.dart index 578083e7..9a03fd62 100644 --- a/packages/spanner/lib/src/tree/tree.dart +++ b/packages/spanner/lib/src/tree/tree.dart @@ -37,7 +37,8 @@ class Spanner { List get routes => _getRoutes(_root); - String get routeStr => routes.map((e) => '${e.method.name} ${e.path}').join('\n'); + String get routeStr => + routes.map((e) => '${e.method.name} ${e.path}').join('\n'); void addRoute(HTTPMethod method, String path, T handler) { final indexedHandler = (index: _nextIndex, value: handler); @@ -82,7 +83,10 @@ class Spanner { if (wildCardNode != null) return wildCardNode..terminal = true; wildCardNode = WildcardNode(); - (rootNode as StaticNode).addChildAndReturn(WildcardNode.key, wildCardNode); + (rootNode as StaticNode).addChildAndReturn( + WildcardNode.key, + wildCardNode, + ); return wildCardNode..terminal = true; } @@ -142,7 +146,10 @@ class Spanner { } else if (part.isWildCard) { if (!isLastSegment) { throw ArgumentError.value( - fullPath, null, 'Route definition is not valid. Wildcard must be the end of the route'); + fullPath, + null, + 'Route definition is not valid. Wildcard must be the end of the route', + ); } return node.addChildAndReturn(key, WildcardNode()); @@ -150,7 +157,8 @@ class Spanner { final paramNode = node.paramNode; if (paramNode == null) { - final defn = ParameterDefinition.from(routePart, terminal: isLastSegment); + final defn = + ParameterDefinition.from(routePart, terminal: isLastSegment); final newNode = node.addChildAndReturn(key, ParametricNode(defn)); if (isLastSegment) return defn; @@ -182,7 +190,10 @@ class Spanner { if (path == BASE_PATH) { rootNode as StaticNode; - return RouteResult(resolvedParams, getResults(rootNode.getHandler(method))); + return RouteResult( + resolvedParams, + getResults(rootNode.getHandler(method)), + ); } final debugLog = StringBuffer("\n"); @@ -239,12 +250,16 @@ class Spanner { final hasChild = parametricNode.hasChild(routePart); if (hasChild) { - devlog('- Found Static for -> $routePart'); + devlog( + '- Found Static for -> $routePart', + ); rootNode = parametricNode.getChild(routePart); continue; } - devlog('- Finding Defn for $routePart -> terminal? $isLastPart'); + devlog( + '- Finding Defn for $routePart -> terminal? $isLastPart', + ); final definition = parametricNode.findMatchingDefinition( method, @@ -262,9 +277,12 @@ class Spanner { final definition = parametricNode.definitions.first; if (definition is CompositeParameterDefinition) break; - final remainingPath = routeSegments.sublist(i).join('/'); + /// if we have more path segments, do not pass it as a parameteric value + final partsLeft = routeSegments.sublist(i); + if (partsLeft.length > 1) break; + final name = parametricNode.definitions.first.name; - resolvedParams[name] = remainingPath; + resolvedParams[name] = partsLeft.join('/'); return RouteResult( resolvedParams, @@ -312,7 +330,8 @@ class Spanner { String _cleanPath(String path) { if ([BASE_PATH, WildcardNode.key].contains(path)) return path; if (!path.startsWith(BASE_PATH)) { - throw ArgumentError.value(path, null, 'Route registration must start with `/`'); + throw ArgumentError.value( + path, null, 'Route registration must start with `/`'); } if (config.ignoreDuplicateSlashes) { path = path.replaceAll(RegExp(r'/+'), '/'); @@ -325,7 +344,8 @@ class Spanner { List _getRouteSegments(String route) => route.split('/'); - String _getNodeKey(String part) => part.isParametric ? ParametricNode.key : part; + String _getNodeKey(String part) => + part.isParametric ? ParametricNode.key : part; } class RouteResult { @@ -346,7 +366,8 @@ List _getRoutes(Node node) { final routes = []; void iterateNode(Node node, String prefix) { - final hasTerminalInParametricNode = node is ParametricNode && node.hasTerminal; + final hasTerminalInParametricNode = + node is ParametricNode && node.hasTerminal; if (node.terminal || hasTerminalInParametricNode) { final entries = _getNodeEntries(node, prefix); routes.addAll(entries); @@ -374,10 +395,12 @@ Iterable _getNodeEntries(Node node, String prefix) { final methods = (node as StaticNode).methods; return methods.map((e) => (method: e, path: prefix)); case ParametricNode: - final definitions = (node as ParametricNode).definitions.where((e) => e.terminal); + final definitions = + (node as ParametricNode).definitions.where((e) => e.terminal); final entries = []; for (final defn in definitions) { - final result = defn.methods.map((e) => (method: e, path: prefix)); + final result = + defn.methods.map((e) => (method: e, path: prefix)); entries.addAll(result); } return entries; diff --git a/packages/spanner/test/wildcard_test.dart b/packages/spanner/test/wildcard_test.dart index ce0e9da0..7b668a21 100644 --- a/packages/spanner/test/wildcard_test.dart +++ b/packages/spanner/test/wildcard_test.dart @@ -3,10 +3,28 @@ import 'package:test/test.dart'; void main() { group('wildcard_test', () { + test('*', () { + final router = Spanner() + ..addMiddleware('/api', 3) + ..addRoute(HTTPMethod.GET, '/api/auth/login', 4) + ..addRoute(HTTPMethod.GET, '/api/users/', 5) + ..addRoute(HTTPMethod.ALL, '/*', 'mee-moo'); + + expect( + router.lookup(HTTPMethod.GET, '/api/users/hello')?.values, + [3, 5], + ); + + final results = ['/api', '/api/users', '/api/users/hello/details/home'] + .map((e) => router.lookup(HTTPMethod.GET, e)?.values.join(', ')) + .toList(); + expect(results, ['3', '3', '3']); + }); + test('when wildcard with HTTPMethod.ALL', () { final router = Spanner()..addRoute(HTTPMethod.ALL, '/*', 'mee-moo'); - var result = router.lookup(HTTPMethod.GET, '/hello-world'); + var result = router.lookup(HTTPMethod.GET, '/hello/boy'); expect(result!.values, ['mee-moo']); result = router.lookup(HTTPMethod.DELETE, '/hello'); @@ -38,7 +56,9 @@ void main() { expect(result?.values, ['mee-moo']); }); - test('static route and wildcard on same method with additional HTTPMETHOD.ALL', () { + test( + 'static route and wildcard on same method with additional HTTPMETHOD.ALL', + () { final router = Spanner() ..addRoute(HTTPMethod.GET, '/hello-world', 'foo-bar') ..addRoute(HTTPMethod.GET, '/*', 'mee-moo') From 0e6239da5195eb0921e4b59703b15fc6f6ccbda6 Mon Sep 17 00:00:00 2001 From: Chima Precious Date: Sun, 28 Jan 2024 12:26:31 +0000 Subject: [PATCH 4/7] todo: not needed anymore --- packages/spanner/lib/src/parametric/utils.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/spanner/lib/src/parametric/utils.dart b/packages/spanner/lib/src/parametric/utils.dart index 3e6a714f..ba5bf133 100644 --- a/packages/spanner/lib/src/parametric/utils.dart +++ b/packages/spanner/lib/src/parametric/utils.dart @@ -45,8 +45,6 @@ RegExp buildRegexFromTemplate(String template) { }, ); - /// TODO(codekeyz) figure out if we need to pass the case sensitivity flag - /// from the wider context down here or it's safe to keep it case insensitive. return RegExp(regexPattern, caseSensitive: false); } @@ -74,5 +72,6 @@ extension ParametricDefinitionsExtension on List { sort((a, b) => nullCount[a.hashCode]!.compareTo(nullCount[b.hashCode]!)); } - Iterable get methods => map((e) => e.methods).reduce((val, e) => {...val, ...e}); + Iterable get methods => + map((e) => e.methods).reduce((val, e) => {...val, ...e}); } From 488904a51e6c761171c2f0cc8d13b8b961bce344 Mon Sep 17 00:00:00 2001 From: Chima Precious Date: Sun, 28 Jan 2024 12:59:43 +0000 Subject: [PATCH 5/7] default res.send function --- packages/pharaoh/lib/src/http/response.dart | 25 +++-- .../pharaoh/lib/src/http/response_impl.dart | 61 ++++++------ .../pharaoh/test/http/res.format_test.dart | 20 ++-- packages/pharaoh/test/http/res.send_test.dart | 96 ++++--------------- packages/pharaoh/test/http/res.type_test.dart | 5 +- 5 files changed, 80 insertions(+), 127 deletions(-) diff --git a/packages/pharaoh/lib/src/http/response.dart b/packages/pharaoh/lib/src/http/response.dart index e774e54a..0e629062 100644 --- a/packages/pharaoh/lib/src/http/response.dart +++ b/packages/pharaoh/lib/src/http/response.dart @@ -10,19 +10,23 @@ import 'message.dart'; part 'response_impl.dart'; -final applicationOctetStreamType = ContentType('application', 'octet-stream'); - abstract class Response extends Message { Response(super.body, {super.headers = const {}}); /// Constructs an HTTP Response - static Response create({int? statusCode, Object? body, Encoding? encoding, Map? headers}) => - ResponseImpl._( - body: body == null ? null : Body(body), - ended: false, - statusCode: statusCode, - headers: headers ?? {}, - ); + static Response create({ + int? statusCode, + Object? body, + Encoding? encoding, + Map? headers, + }) { + return ResponseImpl._( + body: body == null ? null : Body(body), + ended: false, + statusCode: statusCode, + headers: headers ?? {}, + ); + } Response header(String headerKey, String headerValue); @@ -30,7 +34,8 @@ abstract class Response extends Message { /// /// [name] and [value] must be composed of valid characters according to RFC /// 6265. - Response cookie(String name, Object? value, [CookieOpts opts = const CookieOpts()]); + Response cookie(String name, Object? value, + [CookieOpts opts = const CookieOpts()]); Response withCookie(Cookie cookie); diff --git a/packages/pharaoh/lib/src/http/response_impl.dart b/packages/pharaoh/lib/src/http/response_impl.dart index 595aab0b..34e14f27 100644 --- a/packages/pharaoh/lib/src/http/response_impl.dart +++ b/packages/pharaoh/lib/src/http/response_impl.dart @@ -85,18 +85,22 @@ class ResponseImpl extends Response { } @override - ResponseImpl movedPermanently(String url) => redirect(url, HttpStatus.movedPermanently); + ResponseImpl movedPermanently(String url) => + redirect(url, HttpStatus.movedPermanently); @override ResponseImpl notModified({Map? headers}) { final existingHeaders = this.headers; - if (headers != null) headers.forEach((key, val) => existingHeaders[key] = val); + if (headers != null) { + headers.forEach((key, val) => existingHeaders[key] = val); + } return ResponseImpl._( ended: true, statusCode: HttpStatus.notModified, headers: existingHeaders - ..removeWhere((name, _) => name.toLowerCase() == HttpHeaders.contentLengthHeader) + ..removeWhere( + (name, _) => name.toLowerCase() == HttpHeaders.contentLengthHeader) ..[HttpHeaders.dateHeader] = formatHttpDate(DateTime.now()), ); } @@ -124,14 +128,17 @@ class ResponseImpl extends Response { } @override - ResponseImpl notFound([String? message]) => json(error(message ?? 'Not found'), statusCode: HttpStatus.notFound); + ResponseImpl notFound([String? message]) => + json(error(message ?? 'Not found'), statusCode: HttpStatus.notFound); @override - ResponseImpl unauthorized({Object? data}) => json(data ?? error('Unauthorized'), statusCode: HttpStatus.unauthorized); + ResponseImpl unauthorized({Object? data}) => + json(data ?? error('Unauthorized'), statusCode: HttpStatus.unauthorized); @override ResponseImpl internalServerError([String? message]) => - json(error(message ?? 'Internal Server Error'), statusCode: HttpStatus.internalServerError); + json(error(message ?? 'Internal Server Error'), + statusCode: HttpStatus.internalServerError); @override ResponseImpl ok([String? data]) => this.end() @@ -139,9 +146,11 @@ class ResponseImpl extends Response { ..body = shelf.Body(data, encoding); @override - ResponseImpl send(Object data) => this.end() - ..headers[HttpHeaders.contentTypeHeader] = _getContentType(data, valueWhenNull: ContentType.html).toString() - ..body = shelf.Body(data); + ResponseImpl send(Object data) { + return this.end() + ..headers[HttpHeaders.contentTypeHeader] ??= ContentType.binary.toString() + ..body = shelf.Body(data); + } @override ResponseImpl end() => ResponseImpl._( @@ -151,30 +160,21 @@ class ResponseImpl extends Response { statusCode: statusCode, ); - ContentType _getContentType(Object data, {required ContentType valueWhenNull}) { - final isBuffer = _isBuffer(data); - final mType = mediaType; - if (mType == null) { - return isBuffer ? applicationOctetStreamType : valueWhenNull; - } - - /// Always use charset :utf-8 unless - /// we have to deal with buffers. - final charset = isBuffer ? mType.parameters['charset'] : 'utf-8'; - return ContentType.parse('${mType.mimeType}; charset=$charset'); - } - - /// TODO research on how to tell if an object is a buffer - bool _isBuffer(Object object) => object is! String; - @override - ResponseImpl format(Request request, Map options) { + ResponseImpl format( + Request request, + Map options, + ) { var reqAcceptType = request.headers[HttpHeaders.acceptHeader]; if (reqAcceptType is Iterable) reqAcceptType = reqAcceptType.join(); + final handler = options[reqAcceptType] ?? options['_']; if (handler == null) { - return json(error('Not Acceptable'), statusCode: HttpStatus.notAcceptable); + return json( + error('Not Acceptable'), + statusCode: HttpStatus.notAcceptable, + ); } return handler.call(this); @@ -198,7 +198,8 @@ class ResponseImpl extends Response { ..headers[HttpHeaders.setCookieHeader] = _cookies; @override - Response render(String name, [Map data = const {}]) => this.end() - ..viewToRender = ViewRenderData(name, data) - ..headers[HttpHeaders.contentTypeHeader] = ContentType.html.toString(); + Response render(String name, [Map data = const {}]) => + this.end() + ..viewToRender = ViewRenderData(name, data) + ..headers[HttpHeaders.contentTypeHeader] = ContentType.html.toString(); } diff --git a/packages/pharaoh/test/http/res.format_test.dart b/packages/pharaoh/test/http/res.format_test.dart index ddfd36de..2b1294d2 100644 --- a/packages/pharaoh/test/http/res.format_test.dart +++ b/packages/pharaoh/test/http/res.format_test.dart @@ -7,13 +7,13 @@ void main() { group('res.format(Map options)', () { test('should respond using :accept provided', () async { final app = Pharaoh() - ..get( - '/', - (req, res) => res.format(req, { - ContentType.text.toString(): (res) => res.ok('Hello World'), - ContentType.html.toString(): (res) => res.send('

Hello World

'), - }), - ); + ..get('/', (req, res) { + return res.format(req, { + 'text/plain; charset=utf-8': (res) => res.ok('Hello World'), + 'text/html; charset=utf-8': (res) => + res.type(ContentType.html).send('

Hello World

'), + }); + }); await (await request(app)) .get( @@ -42,7 +42,8 @@ void main() { '/', (req, res) => res.format(req, { ContentType.text.toString(): (res) => res.ok('Hello World'), - ContentType.html.toString(): (res) => res.send('

Hello World

'), + ContentType.html.toString(): (res) => + res.send('

Hello World

'), '_': (res) => res.json({'message': 'Hello World'}) }), ); @@ -61,7 +62,8 @@ void main() { '/', (req, res) => res.format(req, { ContentType.text.toString(): (res) => res.ok('Hello World'), - ContentType.html.toString(): (res) => res.send('

Hello World

'), + ContentType.html.toString(): (res) => + res.send('

Hello World

'), }), ); diff --git a/packages/pharaoh/test/http/res.send_test.dart b/packages/pharaoh/test/http/res.send_test.dart index 9b72fe98..893cdf29 100644 --- a/packages/pharaoh/test/http/res.send_test.dart +++ b/packages/pharaoh/test/http/res.send_test.dart @@ -7,99 +7,41 @@ import 'package:spookie/spookie.dart'; void main() { group('.send(Object)', () { - test('should send send as html', () async { + test('should default content-Type to octet-stream', () async { final app = Pharaoh(); app.use((req, res, next) { - next(res.send("

Hey

")); + final buffer = Uint8List.fromList(utf8.encode("Hello World")); + next(res.send(buffer)); }); await (await request(app)) .get('/') .expectStatus(200) - .expectBody('

Hey

') - .expectContentType('text/html; charset=utf-8') + .expectBody('Hello World') + .expectContentType('application/octet-stream') .test(); }); test('should not override previous Content-Types', () async { - final app = Pharaoh(); - - app.use((req, res, next) { - next(res.send("

Hey

")); - }); - - await (await request(app)) - .get('/') + final app = Pharaoh() + ..get('/html', (req, res) { + return res.type(ContentType.html).send("

Hey

"); + }) + ..get('/text', (req, res) { + return res.type(ContentType.text).send("Hey"); + }); + + final tester = await request(app); + + await tester + .get('/html') .expectContentType('text/html; charset=utf-8') - .expectStatus(200) - .expectBody('

Hey

') .test(); - }); - - test('should not override previous Content-Types', () async { - final app = Pharaoh(); - - app.use((req, res, next) { - next(res.type(ContentType.text).send("

Hey

")); - }); - - await (await request(app)) - .get('/') - .expectContentType('text/plain; charset=utf-8') - .expectStatus(200) - .expectBody('

Hey

') - .test(); - }); - - test('should override charset in Content-Type', () async { - final app = Pharaoh(); - - app.use((req, res, next) { - res = res.header('content-type', 'text/plain; charset=iso-8859-1'); - - next(res.send('Hey')); - }); - await (await request(app)) - .get('/') - .expectStatus(200) + await tester + .get('/text') .expectContentType('text/plain; charset=utf-8') - .expectBody('Hey') - .test(); - }); - - test('should keep charset in Content-Type for ', () async { - final app = Pharaoh(); - - app.use((req, res, next) { - res = res.header('content-type', 'text/plain; charset=iso-8859-1'); - final buffer = Uint8List.fromList(utf8.encode("Hello World")); - - next(res.send(buffer)); - }); - - await (await request(app)) - .get('/') - .expectStatus(200) - .expectContentType('text/plain; charset=iso-8859-1') - .expectBody('Hello World') - .test(); - }); - - test('should send as octet-stream', () async { - final app = Pharaoh(); - - app.use((req, res, next) { - final buffer = Uint8List.fromList(utf8.encode("Hello World")); - next(res.send(buffer)); - }); - - await (await request(app)) - .get('/') - .expectStatus(200) - .expectBody('Hello World') - .expectContentType('application/octet-stream') .test(); }); }); diff --git a/packages/pharaoh/test/http/res.type_test.dart b/packages/pharaoh/test/http/res.type_test.dart index 97a3739b..b29ed507 100644 --- a/packages/pharaoh/test/http/res.type_test.dart +++ b/packages/pharaoh/test/http/res.type_test.dart @@ -8,7 +8,10 @@ void main() { test('should set the Content-Type with type/subtype', () async { final app = Pharaoh() ..get('/', (req, res) { - return res.type(ContentType.parse('application/vnd.amazon.ebook')).send('var name = "tj";'); + final cType = + ContentType('application', 'vnd.amazon.ebook', charset: 'utf-8'); + + return res.type(cType).send('var name = "tj";'); }); await (await request(app)) From 3fdcf72102495cb3765c82d420c3e3d7b56ba8c1 Mon Sep 17 00:00:00 2001 From: Chima Precious Date: Sun, 28 Jan 2024 13:05:14 +0000 Subject: [PATCH 6/7] no longer need this test --- packages/spanner/test/parametric_test.dart | 51 +++++++++++----------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/packages/spanner/test/parametric_test.dart b/packages/spanner/test/parametric_test.dart index 70af5af6..224db2da 100644 --- a/packages/spanner/test/parametric_test.dart +++ b/packages/spanner/test/parametric_test.dart @@ -16,21 +16,25 @@ void main() { ..addRoute(HTTPMethod.GET, '/user/.png//hello', null); final exception = runSyncAndReturnException(router); - expect(exception.message, contains('Route has inconsistent naming in parameter definition')); + expect(exception.message, + contains('Route has inconsistent naming in parameter definition')); expect(exception.message, contains('.png')); expect(exception.message, contains('.png')); }); test('close door parameter definitions', () { - router() => Spanner()..addRoute(HTTPMethod.GET, '/user/', null); + router() => + Spanner()..addRoute(HTTPMethod.GET, '/user/', null); final exception = runSyncAndReturnException(router); - expect(exception.message, contains('Parameter definition is invalid. Close door neighbors')); + expect(exception.message, + contains('Parameter definition is invalid. Close door neighbors')); expect(exception.invalidValue, ''); }); test('invalid parameter definition', () { - router() => Spanner()..addRoute(HTTPMethod.GET, '/user/>#>', null); + router() => Spanner() + ..addRoute(HTTPMethod.GET, '/user/>#>', null); final exception = runSyncAndReturnException(router); expect(exception.message, contains('Parameter definition is invalid')); @@ -77,7 +81,10 @@ void main() { expect(node, havingParameters({'file': 'aws-image'})); node = router.lookup(HTTPMethod.GET, '/user/aws-image.png/A29384/hello'); - expect(node, havingParameters({'file': 'aws-image', 'user2': 'A29384'})); + expect( + node, + havingParameters( + {'file': 'aws-image', 'user2': 'A29384'})); node = router.lookup(HTTPMethod.GET, '/a/param-static'); expect(node, havingParameters({'param': 'param'})); @@ -102,30 +109,20 @@ void main() { ); }); - test('should capture remaining parts as parameter when no wildcard', () { - final router = Spanner()..addRoute(HTTPMethod.GET, '//item/', null); - - expect( - router.lookup(HTTPMethod.GET, '/fr/item/12345'), - havingParameters({'lang': 'fr', 'id': '12345'}), - ); - - expect( - router.lookup(HTTPMethod.GET, '/fr/item/12345/edit'), - havingParameters({'lang': 'fr', 'id': '12345/edit'}), - ); - }); - group('when descriptors', () { test('in single parametric definition', () { - final router = Spanner()..addRoute(HTTPMethod.GET, '/users//detail', null); + final router = Spanner() + ..addRoute( + HTTPMethod.GET, '/users//detail', null); - expect(router.lookup(HTTPMethod.GET, '/users/24/detail'), havingParameters({'userId': 24})); + expect(router.lookup(HTTPMethod.GET, '/users/24/detail'), + havingParameters({'userId': 24})); }); test('should error when invalid params', () { final router = Spanner() - ..addRoute(HTTPMethod.GET, '/users//detail', null) + ..addRoute( + HTTPMethod.GET, '/users//detail', null) ..addRoute(HTTPMethod.GET, '/', null); var result = router.lookup(HTTPMethod.GET, '/users/24/detail'); @@ -135,13 +132,17 @@ void main() { expect(result, havingParameters({'userId': 'hello-world'})); expect( - runSyncAndReturnException(() => router.lookup(HTTPMethod.GET, '/@388>)#(***)')), - isA().having((p0) => p0.message, 'with message', 'Invalid parameter value'), + runSyncAndReturnException( + () => router.lookup(HTTPMethod.GET, '/@388>)#(***)')), + isA().having( + (p0) => p0.message, 'with message', 'Invalid parameter value'), ); }); test('in composite parametric definition', () async { - final router = Spanner()..addRoute(HTTPMethod.GET, '/users/HELLO/detail', null); + final router = Spanner() + ..addRoute(HTTPMethod.GET, + '/users/HELLO/detail', null); var result = router.lookup(HTTPMethod.GET, '/users/334HELLO387/detail'); expect(result, havingParameters({'userId': 334, 'paramId': 387})); From aaebad2a318fbf3aa12937bc99bde23164f44364 Mon Sep 17 00:00:00 2001 From: Chima Precious Date: Sun, 28 Jan 2024 13:09:12 +0000 Subject: [PATCH 7/7] fix code formatting issues --- melos.yaml | 2 +- packages/pharaoh/lib/src/core.dart | 3 +- packages/pharaoh/lib/src/http/cookie.dart | 7 +- .../pharaoh/lib/src/http/request_impl.dart | 6 +- .../lib/src/middleware/body_parser.dart | 3 +- .../lib/src/middleware/cookie_parser.dart | 4 +- .../lib/src/middleware/session_mw.dart | 6 +- .../lib/src/router/_handler_executor.dart | 3 +- packages/pharaoh/lib/src/router/router.dart | 8 +- .../lib/src/router/router_handler.dart | 3 +- .../lib/src/shelf_interop/adapter.dart | 3 +- packages/pharaoh/lib/src/utils/utils.dart | 3 +- .../pharaoh/test/http/req.query_test.dart | 3 +- .../pharaoh/test/http/res.cookie_test.dart | 40 ++++++-- packages/pharaoh/test/http/res.json_test.dart | 18 +++- .../pharaoh/test/http/res.status_test.dart | 9 +- .../test/middleware/body_parser_test.dart | 21 ++++- .../test/middleware/cookie_parser_test.dart | 30 ++++-- .../pharaoh/test/router/handler_test.dart | 16 +++- .../test/router/router_group_test.dart | 6 +- .../test/basic_auth_test.dart | 28 ++++-- .../test/pharaoh_jwt_auth_test.dart | 3 +- .../lib/src/parametric/definition.dart | 16 +++- .../lib/src/parametric/descriptor.dart | 3 +- packages/spanner/lib/src/route/action.dart | 3 +- packages/spanner/lib/src/tree/node.dart | 6 +- .../spanner/test/case_insensitive_test.dart | 35 ++++--- packages/spookie/example/spookie_example.dart | 4 +- packages/spookie/lib/spookie.dart | 9 +- .../spookie/lib/src/http_expectation.dart | 20 ++-- packages/spookie/test/spookie_test.dart | 92 +++++++++++++++---- pharaoh_examples/test/api_service_test.dart | 18 ++-- 32 files changed, 318 insertions(+), 113 deletions(-) diff --git a/melos.yaml b/melos.yaml index 2c456cee..189300f0 100644 --- a/melos.yaml +++ b/melos.yaml @@ -25,6 +25,6 @@ scripts: melos exec -- "dart pub global run coverage:format_coverage --check-ignore --report-on=lib --lcov -o "$MELOS_ROOT_PATH/coverage/$(echo "\$MELOS_PACKAGE_NAME")_lcov.info" -i ./coverage" find $MELOS_ROOT_PATH/coverage -type f -empty -print -delete - format: melos exec -- "dart format . --line-length=120" + format: melos exec -- "dart format ." analyze: melos exec -- "dart analyze . --fatal-infos" diff --git a/packages/pharaoh/lib/src/core.dart b/packages/pharaoh/lib/src/core.dart index 2f9b7dd0..12201ed6 100644 --- a/packages/pharaoh/lib/src/core.dart +++ b/packages/pharaoh/lib/src/core.dart @@ -22,7 +22,8 @@ import 'shelf_interop/shelf.dart' as shelf; part 'core_impl.dart'; -typedef OnErrorCallback = FutureOr Function(Object error, Request req, Response res); +typedef OnErrorCallback = FutureOr Function( + Object error, Request req, Response res); abstract class Pharaoh implements RouterContract { factory Pharaoh() => $PharaohImpl(); diff --git a/packages/pharaoh/lib/src/http/cookie.dart b/packages/pharaoh/lib/src/http/cookie.dart index 1d3c9863..227cfd8d 100644 --- a/packages/pharaoh/lib/src/http/cookie.dart +++ b/packages/pharaoh/lib/src/http/cookie.dart @@ -56,7 +56,8 @@ class CookieOpts { void validate() { if (signed && secret == null) { - throw PharaohException.value('CookieOpts("secret") required for signed cookies'); + throw PharaohException.value( + 'CookieOpts("secret") required for signed cookies'); } } } @@ -77,7 +78,9 @@ extension CookieExtension on Cookie { bool get signed => decodedValue.startsWith('s:'); - String get actualStr => signed ? decodedValue.substring(2) : decodedValue; // s:foo-bar-baz --> foo-bar-bar + String get actualStr => signed + ? decodedValue.substring(2) + : decodedValue; // s:foo-bar-baz --> foo-bar-bar bool get jsonEncoded => actualStr.startsWith('j:'); diff --git a/packages/pharaoh/lib/src/http/request_impl.dart b/packages/pharaoh/lib/src/http/request_impl.dart index b5bd935f..6418225e 100644 --- a/packages/pharaoh/lib/src/http/request_impl.dart +++ b/packages/pharaoh/lib/src/http/request_impl.dart @@ -39,7 +39,8 @@ class RequestImpl extends Request { String get path => actual.uri.path; @override - String get ipAddr => actual.connectionInfo?.remoteAddress.address ?? 'Unknown'; + String get ipAddr => + actual.connectionInfo?.remoteAddress.address ?? 'Unknown'; @override HTTPMethod get method => getHttpMethod(actual); @@ -63,7 +64,8 @@ class RequestImpl extends Request { List get cookies => _context[RequestContext.cookies] ?? []; @override - List get signedCookies => _context[RequestContext.signedCookies] ?? []; + List get signedCookies => + _context[RequestContext.signedCookies] ?? []; @override Session? get session => _context[RequestContext.session]; diff --git a/packages/pharaoh/lib/src/middleware/body_parser.dart b/packages/pharaoh/lib/src/middleware/body_parser.dart index b7aa2119..243ce672 100644 --- a/packages/pharaoh/lib/src/middleware/body_parser.dart +++ b/packages/pharaoh/lib/src/middleware/body_parser.dart @@ -9,7 +9,8 @@ import '../router/router_handler.dart'; class MimeType { static const String multiPartForm = 'multipart/form-data'; - static const String applicationFormUrlEncoded = 'application/x-www-form-urlencoded'; + static const String applicationFormUrlEncoded = + 'application/x-www-form-urlencoded'; static const String applicationJson = 'application/json'; static const String textPlain = 'text/plain'; } diff --git a/packages/pharaoh/lib/src/middleware/cookie_parser.dart b/packages/pharaoh/lib/src/middleware/cookie_parser.dart index 7e4dfe47..6db38c3c 100644 --- a/packages/pharaoh/lib/src/middleware/cookie_parser.dart +++ b/packages/pharaoh/lib/src/middleware/cookie_parser.dart @@ -22,7 +22,9 @@ Middleware cookieParser({CookieOpts opts = const CookieOpts()}) { for (final cookie in signedCookies) { var realValue = unsignValue(cookie.actualStr, secret); - if (realValue != null) verifiedCookies.add(cookie..value = Uri.encodeComponent(realValue)); + if (realValue != null) { + verifiedCookies.add(cookie..value = Uri.encodeComponent(realValue)); + } } signedCookies = verifiedCookies; } diff --git a/packages/pharaoh/lib/src/middleware/session_mw.dart b/packages/pharaoh/lib/src/middleware/session_mw.dart index 7c811d7b..cd0f7f01 100644 --- a/packages/pharaoh/lib/src/middleware/session_mw.dart +++ b/packages/pharaoh/lib/src/middleware/session_mw.dart @@ -69,7 +69,8 @@ Middleware sessionMdw({ if (!req.path.startsWith(opts.path)) return next(); if (req.session?.valid ?? false) return next(); - final reqSid = req.signedCookies.firstWhereOrNull((e) => e.name == name)?.value; + final reqSid = + req.signedCookies.firstWhereOrNull((e) => e.name == name)?.value; if (reqSid != null) { var result = await sessionStore.get(reqSid); if (result != null && result.valid) { @@ -94,7 +95,8 @@ Middleware sessionMdw({ final ReqResHook sessionPreResponseHook = (ReqRes reqRes) async { var req = reqRes.req, res = reqRes.res; final session = req.session; - if (session != null && (session.saveUninitialized || session.resave || session.modified)) { + if (session != null && + (session.saveUninitialized || session.resave || session.modified)) { await session.save(); res = res.withCookie(session.cookie!); } diff --git a/packages/pharaoh/lib/src/router/_handler_executor.dart b/packages/pharaoh/lib/src/router/_handler_executor.dart index 488519fc..e6bf1732 100644 --- a/packages/pharaoh/lib/src/router/_handler_executor.dart +++ b/packages/pharaoh/lib/src/router/_handler_executor.dart @@ -36,7 +36,8 @@ final class Executor { } Future _resetStream() async { - void newStream() => _streamCtrl = StreamController()..add(_handler); + void newStream() => + _streamCtrl = StreamController()..add(_handler); final ctrl = _streamCtrl; if (ctrl == null) return newStream(); if (ctrl.hasListener && ctrl.isClosed) await ctrl.close(); diff --git a/packages/pharaoh/lib/src/router/router.dart b/packages/pharaoh/lib/src/router/router.dart index b4367d10..f506e6fd 100644 --- a/packages/pharaoh/lib/src/router/router.dart +++ b/packages/pharaoh/lib/src/router/router.dart @@ -3,7 +3,10 @@ import 'package:spanner/spanner.dart'; import 'router_contract.dart'; import 'router_handler.dart'; -typedef _PendingRouteIntent = (HTTPMethod method, ({String path, Middleware handler})); +typedef _PendingRouteIntent = ( + HTTPMethod method, + ({String path, Middleware handler}) +); class GroupRouter extends RouterContract { final List<_PendingRouteIntent> _pendingRouteIntents = []; @@ -89,7 +92,8 @@ class GroupRouter extends RouterContract { } @override - GroupRouter on(String path, Middleware func, {HTTPMethod method = HTTPMethod.ALL}) { + GroupRouter on(String path, Middleware func, + {HTTPMethod method = HTTPMethod.ALL}) { if (method == HTTPMethod.ALL) path = '$path/*'; _pendingRouteIntents.add((method, (path: path, handler: func))); return this; diff --git a/packages/pharaoh/lib/src/router/router_handler.dart b/packages/pharaoh/lib/src/router/router_handler.dart index 16aa730a..ca4f6300 100644 --- a/packages/pharaoh/lib/src/router/router_handler.dart +++ b/packages/pharaoh/lib/src/router/router_handler.dart @@ -43,7 +43,8 @@ extension HandlerChainExtension on Middleware { ); } -Middleware useRequestHandler(RequestHandler handler) => (req, res, next_) async { +Middleware useRequestHandler(RequestHandler handler) => + (req, res, next_) async { final result = await handler(req, res); next_(result); }; diff --git a/packages/pharaoh/lib/src/shelf_interop/adapter.dart b/packages/pharaoh/lib/src/shelf_interop/adapter.dart index 4cbd8e5d..e555e159 100644 --- a/packages/pharaoh/lib/src/shelf_interop/adapter.dart +++ b/packages/pharaoh/lib/src/shelf_interop/adapter.dart @@ -18,7 +18,8 @@ typedef ShelfMiddlewareType2 = FutureOr Function(shelf.Request); Middleware useShelfMiddleware(dynamic middleware) { if (middleware is shelf.Middleware) { return (req, res, next) async { - final shelfResponse = await middleware((req) => shelf.Response.ok(req.read()))(_toShelfRequest(req)); + final shelfResponse = await middleware( + (req) => shelf.Response.ok(req.read()))(_toShelfRequest(req)); res = _fromShelfResponse((req: req, res: res), shelfResponse); next(res); diff --git a/packages/pharaoh/lib/src/utils/utils.dart b/packages/pharaoh/lib/src/utils/utils.dart index 05c87704..9dfc34b0 100644 --- a/packages/pharaoh/lib/src/utils/utils.dart +++ b/packages/pharaoh/lib/src/utils/utils.dart @@ -12,7 +12,8 @@ String contentTypeToString(ContentType type, {String charset = 'utf-8'}) { /// If `this` is called in a non-root error zone, it will just run [callback] /// and return the result. Otherwise, it will capture any errors using /// [runZoned] and pass them to [onError]. -void catchTopLevelErrors(void Function() callback, void Function(dynamic error, StackTrace) onError) { +void catchTopLevelErrors(void Function() callback, + void Function(dynamic error, StackTrace) onError) { if (Zone.current.inSameErrorZone(Zone.root)) { return runZonedGuarded(callback, onError); } else { diff --git a/packages/pharaoh/test/http/req.query_test.dart b/packages/pharaoh/test/http/req.query_test.dart index d6e49093..fe34bc70 100644 --- a/packages/pharaoh/test/http/req.query_test.dart +++ b/packages/pharaoh/test/http/req.query_test.dart @@ -14,7 +14,8 @@ void main() { }); test('should pass a param', () async { - final app = Pharaoh()..get('/', (req, res) => res.json(req.params)); + final app = Pharaoh() + ..get('/', (req, res) => res.json(req.params)); await (await request(app)) .get('/heyOnuoha') diff --git a/packages/pharaoh/test/http/res.cookie_test.dart b/packages/pharaoh/test/http/res.cookie_test.dart index fe24c3cf..8f19f3ec 100644 --- a/packages/pharaoh/test/http/res.cookie_test.dart +++ b/packages/pharaoh/test/http/res.cookie_test.dart @@ -6,7 +6,8 @@ import 'package:spookie/spookie.dart'; void main() { group('res.cookie(name, string)', () { test('should set a cookie', () async { - final app = Pharaoh()..get('/', (req, res) => res.cookie('name', 'chima').end()); + final app = Pharaoh() + ..get('/', (req, res) => res.cookie('name', 'chima').end()); await (await request(app)) .get('/') @@ -17,12 +18,19 @@ void main() { test('should allow multiple calls', () async { final app = Pharaoh() - ..get('/', (req, res) => res.cookie('name', 'chima').cookie('age', '1').cookie('gender', '?').end()); + ..get( + '/', + (req, res) => res + .cookie('name', 'chima') + .cookie('age', '1') + .cookie('gender', '?') + .end()); await (await request(app)) .get('/') .expectStatus(200) - .expectHeader(HttpHeaders.setCookieHeader, 'name=chima; Path=/,age=1; Path=/,gender=%3F; Path=/') + .expectHeader(HttpHeaders.setCookieHeader, + 'name=chima; Path=/,age=1; Path=/,gender=%3F; Path=/') .test(); }); }); @@ -31,20 +39,26 @@ void main() { test('should set :httpOnly or :secure', () async { final app = Pharaoh() ..get('/', (req, res) { - return res.cookie('name', 'chima', CookieOpts(httpOnly: true, secure: true)).end(); + return res + .cookie('name', 'chima', CookieOpts(httpOnly: true, secure: true)) + .end(); }); await (await request(app)) .get('/') .expectStatus(200) - .expectHeader(HttpHeaders.setCookieHeader, 'name=chima; Path=/; Secure; HttpOnly') + .expectHeader(HttpHeaders.setCookieHeader, + 'name=chima; Path=/; Secure; HttpOnly') .test(); }); test('should set :maxAge', () async { final app = Pharaoh() ..get('/', (req, res) { - return res.cookie('name', 'chima', CookieOpts(maxAge: const Duration(seconds: 5))).end(); + return res + .cookie('name', 'chima', + CookieOpts(maxAge: const Duration(seconds: 5))) + .end(); }); await (await request(app)) @@ -58,7 +72,10 @@ void main() { test('should set :signed', () async { final app = Pharaoh() ..get('/', (req, res) { - return res.cookie('user', {"name": 'tobi'}, CookieOpts(signed: true, secret: 'foo bar baz')).end(); + return res + .cookie('user', {"name": 'tobi'}, + CookieOpts(signed: true, secret: 'foo bar baz')) + .end(); }); await (await request(app)) @@ -71,12 +88,17 @@ void main() { test('should reject when :signed without :secret', () async { final app = Pharaoh() - ..get('/', (req, res) => res.cookie('user', {"name": 'tobi'}, CookieOpts(signed: true)).end()); + ..get( + '/', + (req, res) => res + .cookie('user', {"name": 'tobi'}, CookieOpts(signed: true)) + .end()); await (await request(app)) .get('/') .expectStatus(500) - .expectBody(contains('CookieOpts(\\"secret\\") required for signed cookies')) + .expectBody( + contains('CookieOpts(\\"secret\\") required for signed cookies')) .test(); }); }); diff --git a/packages/pharaoh/test/http/res.json_test.dart b/packages/pharaoh/test/http/res.json_test.dart index 780f95c0..1495f25d 100644 --- a/packages/pharaoh/test/http/res.json_test.dart +++ b/packages/pharaoh/test/http/res.json_test.dart @@ -8,7 +8,9 @@ void main() { test('should not override previous Content-Types', () async { final app = Pharaoh() ..get('/', (req, res) { - return res.type(ContentType.parse('application/vnd.example+json')).json({"hello": "world"}); + return res + .type(ContentType.parse('application/vnd.example+json')) + .json({"hello": "world"}); }); await (await request(app)) @@ -25,7 +27,9 @@ void main() { await (await request(app)) .get('/') .expectStatus(500) - .expectBody({'error': "Converting object to an encodable object failed: Never"}) + .expectBody({ + 'error': "Converting object to an encodable object failed: Never" + }) .expectContentType('application/json; charset=utf-8') .test(); }); @@ -89,7 +93,8 @@ void main() { group('when given a collection type', () { test(' should respond with json', () async { - final app = Pharaoh()..use((req, res, next) => next(res.json(["foo", "bar", "baz"]))); + final app = Pharaoh() + ..use((req, res, next) => next(res.json(["foo", "bar", "baz"]))); await (await request(app)) .get('/') @@ -100,7 +105,9 @@ void main() { }); test(' should respond with json', () async { - final app = Pharaoh()..use((req, res, next) => next(res.json({"name": "Foo bar", "age": 23.45}))); + final app = Pharaoh() + ..use((req, res, next) => + next(res.json({"name": "Foo bar", "age": 23.45}))); await (await request(app)) .get('/') @@ -111,7 +118,8 @@ void main() { }); test(' should respond with json', () async { - final app = Pharaoh()..use((req, res, next) => next(res.json({"Chima", "Foo", "Bar"}))); + final app = Pharaoh() + ..use((req, res, next) => next(res.json({"Chima", "Foo", "Bar"}))); await (await request(app)) .get('/') diff --git a/packages/pharaoh/test/http/res.status_test.dart b/packages/pharaoh/test/http/res.status_test.dart index 6c2d1c0b..54a92c39 100644 --- a/packages/pharaoh/test/http/res.status_test.dart +++ b/packages/pharaoh/test/http/res.status_test.dart @@ -47,7 +47,8 @@ void main() { try { await (await request(app)).get('/').test(); } catch (e) { - expect((e as StateError).message, 'Response has no Location header for redirect'); + expect((e as StateError).message, + 'Response has no Location header for redirect'); } }); }); @@ -58,6 +59,10 @@ void main() { ..get('/hello', (req, res) => res.ok('Hello World')) ..get('/users', (req, res) => res.redirect('/hello')); - await (await request(app)).get('/users').expectStatus(HttpStatus.ok).expectBody('Hello World').test(); + await (await request(app)) + .get('/users') + .expectStatus(HttpStatus.ok) + .expectBody('Hello World') + .test(); }); } diff --git a/packages/pharaoh/test/middleware/body_parser_test.dart b/packages/pharaoh/test/middleware/body_parser_test.dart index 53b9d4c5..0ae0c09e 100644 --- a/packages/pharaoh/test/middleware/body_parser_test.dart +++ b/packages/pharaoh/test/middleware/body_parser_test.dart @@ -18,17 +18,20 @@ void main() { final app = Pharaoh()..post('/', (req, res) => res.json(req.body)); await (await request(app)) - .post('/', '{"name":"Chima","age":24}', headers: {'Content-Type': 'application/json'}) + .post('/', '{"name":"Chima","age":24}', + headers: {'Content-Type': 'application/json'}) .expectStatus(200) .expectBody({'name': 'Chima', 'age': 24}) .test(); }); - test('when content-type is `application/x-www-form-urlencoded`', () async { + test('when content-type is `application/x-www-form-urlencoded`', + () async { final app = Pharaoh()..post('/', (req, res) => res.json(req.body)); await (await request(app)) - .post('/', 'name%3DChima%26age%3D24', headers: {'Content-Type': 'application/x-www-form-urlencoded'}) + .post('/', 'name%3DChima%26age%3D24', + headers: {'Content-Type': 'application/x-www-form-urlencoded'}) .expectStatus(200) .expectBody({'name': 'Chima', 'age': '24'}) .test(); @@ -39,13 +42,21 @@ void main() { test('when request body is null', () async { final app = Pharaoh()..post('/', (req, res) => res.json(req.body)); - await (await request(app)).post('/', null).expectStatus(200).expectBody('null').test(); + await (await request(app)) + .post('/', null) + .expectStatus(200) + .expectBody('null') + .test(); }); test('when request body is empty', () async { final app = Pharaoh()..post('/', (req, res) => res.json(req.body)); - await (await request(app)).post('/', '').expectStatus(200).expectBody('null').test(); + await (await request(app)) + .post('/', '') + .expectStatus(200) + .expectBody('null') + .test(); }); }); }); diff --git a/packages/pharaoh/test/middleware/cookie_parser_test.dart b/packages/pharaoh/test/middleware/cookie_parser_test.dart index fd02e6f4..7cf3ce55 100644 --- a/packages/pharaoh/test/middleware/cookie_parser_test.dart +++ b/packages/pharaoh/test/middleware/cookie_parser_test.dart @@ -31,8 +31,10 @@ void main() { }); await (await request(app)) - .get('/', - headers: {HttpHeaders.cookieHeader: 'name=s%3Achima.4ytL9j25i8e59B6eCUUZdrWHdGLK3Cua%2BG1oGyurzXY'}) + .get('/', headers: { + HttpHeaders.cookieHeader: + 'name=s%3Achima.4ytL9j25i8e59B6eCUUZdrWHdGLK3Cua%2BG1oGyurzXY' + }) .expectStatus(200) .expectBody('[name=chima; HttpOnly]') .test(); @@ -49,7 +51,8 @@ void main() { await (await request(app)) .get('/', headers: { - HttpHeaders.cookieHeader: 'name=s%3Achimaxyz.4ytL9j25i8e59B6eCUUZdrWHdGLK3Cua%2BG1oGyurzXY; Path=/' + HttpHeaders.cookieHeader: + 'name=s%3Achimaxyz.4ytL9j25i8e59B6eCUUZdrWHdGLK3Cua%2BG1oGyurzXY; Path=/' }) .expectStatus(200) .expectBody('[]') @@ -82,7 +85,11 @@ void main() { return res.ok(str); }); - await (await request(app)).get('/').expectStatus(200).expectBody('[]').test(); + await (await request(app)) + .get('/') + .expectStatus(200) + .expectBody('[]') + .test(); }); test('should default req.signedCookies to []', () async { @@ -93,14 +100,19 @@ void main() { return res.ok(str); }); - await (await request(app)).get('/').expectStatus(200).expectBody('[]').test(); + await (await request(app)) + .get('/') + .expectStatus(200) + .expectBody('[]') + .test(); }); }); group('when json-encoded value', () { test('should parse when signed', () async { final cookieOpts = CookieOpts(signed: true, secret: 'foo-bar-mee-moo'); - final cookie = bakeCookie('user', {'foo': 'bar', 'mee': 'mee'}, cookieOpts); + final cookie = + bakeCookie('user', {'foo': 'bar', 'mee': 'mee'}, cookieOpts); expect(cookie.toString(), 'user=s%3Aj%3A%7B%22foo%22%3A%22bar%22%2C%22mee%22%3A%22mee%22%7D.sxYOqZyRsCeSGNGzAR5UG3Hv%2BW%2BiXl9TQPlbbdBLMF0; Path=/'); @@ -122,9 +134,11 @@ void main() { test('should parse when un-signed', () async { final cookieOpts = CookieOpts(signed: false); - final cookie = bakeCookie('user', {'foo': 'bar', 'mee': 'mee'}, cookieOpts); + final cookie = + bakeCookie('user', {'foo': 'bar', 'mee': 'mee'}, cookieOpts); - expect(cookie.toString(), 'user=j%3A%7B%22foo%22%3A%22bar%22%2C%22mee%22%3A%22mee%22%7D; Path=/'); + expect(cookie.toString(), + 'user=j%3A%7B%22foo%22%3A%22bar%22%2C%22mee%22%3A%22mee%22%7D; Path=/'); expect(cookie.signed, isFalse); diff --git a/packages/pharaoh/test/router/handler_test.dart b/packages/pharaoh/test/router/handler_test.dart index 3b97f5ef..2a06686d 100644 --- a/packages/pharaoh/test/router/handler_test.dart +++ b/packages/pharaoh/test/router/handler_test.dart @@ -11,7 +11,11 @@ void main() { }) ..get('/', (req, res) => res.send(req.auth)); - await (await request(app)).get('/').expectStatus(200).expectBody('some-token').test(); + await (await request(app)) + .get('/') + .expectStatus(200) + .expectBody('some-token') + .test(); }); test('should deliver res', () async { @@ -21,7 +25,11 @@ void main() { return next(res.ok('Hello World')); }); - await (await request(app)).get('/').expectStatus(200).expectBody('Hello World').test(); + await (await request(app)) + .get('/') + .expectStatus(200) + .expectBody('Hello World') + .test(); }); test('should deliver both :req and :res', () async { @@ -89,7 +97,9 @@ void main() { listResultList.clear(); - final shortLivedChain = testChain3.chain((req, res, next) => next(res.end())).chain(testChain2); + final shortLivedChain = testChain3 + .chain((req, res, next) => next(res.end())) + .chain(testChain2); await (await request(getApp(shortLivedChain))).get('/test').test(); expect(listResultList, [3, 1, 3, 2, 1]); diff --git a/packages/pharaoh/test/router/router_group_test.dart b/packages/pharaoh/test/router/router_group_test.dart index 05f568af..8d96fd52 100644 --- a/packages/pharaoh/test/router/router_group_test.dart +++ b/packages/pharaoh/test/router/router_group_test.dart @@ -23,7 +23,11 @@ void main() { .expectStatus(200) .test(); - await (await request(app)).get('/admin').expectBody('Holy Moly 🚀').expectStatus(200).test(); + await (await request(app)) + .get('/admin') + .expectBody('Holy Moly 🚀') + .expectStatus(200) + .test(); }); }); } diff --git a/packages/pharaoh_basic_auth/test/basic_auth_test.dart b/packages/pharaoh_basic_auth/test/basic_auth_test.dart index 79191cb7..d4c140a3 100644 --- a/packages/pharaoh_basic_auth/test/basic_auth_test.dart +++ b/packages/pharaoh_basic_auth/test/basic_auth_test.dart @@ -47,12 +47,20 @@ void main() { test( 'should reject on wrong credentials', - () async => (await request(app)).auth('dude', 'stuff').get(endpoint).expectStatus(401).test(), + () async => (await request(app)) + .auth('dude', 'stuff') + .get(endpoint) + .expectStatus(401) + .test(), ); test( 'should reject on shorter prefix', - () async => (await request(app)).auth('Admin', 'secret').get(endpoint).expectStatus(401).test(), + () async => (await request(app)) + .auth('Admin', 'secret') + .get(endpoint) + .expectStatus(401) + .test(), ); test( @@ -67,8 +75,11 @@ void main() { test( 'should accept correct credentials', - () async => - await (await request(app)).auth('Admin', 'secret1234').get(endpoint).expectStatus(200).test(), + () async => await (await request(app)) + .auth('Admin', 'secret1234') + .get(endpoint) + .expectStatus(200) + .test(), ); }); @@ -128,7 +139,8 @@ void main() { setUp(() { bool myComparingAuthorizer(username, password) => - safeCompare(username, 'Testeroni') && safeCompare(password, 'testsecret'); + safeCompare(username, 'Testeroni') && + safeCompare(password, 'testsecret'); final customAuth = basicAuth(authorizer: myComparingAuthorizer); app = Pharaoh() @@ -138,7 +150,11 @@ void main() { test( 'should reject wrong credentials', - () async => (await request(app)).auth('bla', 'blub').get(endpoint).expectStatus(401).test(), + () async => (await request(app)) + .auth('bla', 'blub') + .get(endpoint) + .expectStatus(401) + .test(), ); test( diff --git a/packages/pharaoh_jwt_auth/test/pharaoh_jwt_auth_test.dart b/packages/pharaoh_jwt_auth/test/pharaoh_jwt_auth_test.dart index 75b4cf9e..70a3c582 100644 --- a/packages/pharaoh_jwt_auth/test/pharaoh_jwt_auth_test.dart +++ b/packages/pharaoh_jwt_auth/test/pharaoh_jwt_auth_test.dart @@ -43,7 +43,8 @@ void main() { 'user': {"name": 'Foo', 'lastname': 'Bar'} }, issuer: 'https://github.com/jonasroussel/dart_jsonwebtoken'); - final token = jwt.sign(secretKey, algorithm: JWTAlgorithm.HS256, expiresIn: Duration(seconds: 1)); + final token = jwt.sign(secretKey, + algorithm: JWTAlgorithm.HS256, expiresIn: Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1)); diff --git a/packages/spanner/lib/src/parametric/definition.dart b/packages/spanner/lib/src/parametric/definition.dart index c6cc6620..d3fcc14f 100644 --- a/packages/spanner/lib/src/parametric/definition.dart +++ b/packages/spanner/lib/src/parametric/definition.dart @@ -10,7 +10,8 @@ final _knownDescriptors = {'number': numDescriptor}; /// build a parametric definition from a route part ParameterDefinition? _buildParamDefinition(String part, bool terminal) { if (closeDoorParametricRegex.hasMatch(part)) { - throw ArgumentError.value(part, null, 'Parameter definition is invalid. Close door neighbors'); + throw ArgumentError.value( + part, null, 'Parameter definition is invalid. Close door neighbors'); } ParameterDefinition makeDefinition(RegExpMatch m, {bool end = false}) { @@ -21,7 +22,8 @@ ParameterDefinition? _buildParamDefinition(String part, bool terminal) { final result = parts.sublist(1).map((e) { final value = e.isRegex ? regexDescriptor : _knownDescriptors[e]; if (value == null) { - throw ArgumentError.value(e, null, 'Parameter definition has invalid descriptor'); + throw ArgumentError.value( + e, null, 'Parameter definition has invalid descriptor'); } return value; }); @@ -52,7 +54,8 @@ ParameterDefinition? _buildParamDefinition(String part, bool terminal) { (i, e) => makeDefinition(e, end: i == (subdefns.length - 1) && terminal), ); - return CompositeParameterDefinition._(parent, subparts: UnmodifiableListView(subparts)); + return CompositeParameterDefinition._(parent, + subparts: UnmodifiableListView(subparts)); } class ParameterDefinition with EquatableMixin, HandlerStore { @@ -96,7 +99,9 @@ class ParameterDefinition with EquatableMixin, HandlerStore { if (!hasMethod) return false; } - return prefix == defn.prefix && suffix == defn.suffix && terminal == defn.terminal; + return prefix == defn.prefix && + suffix == defn.suffix && + terminal == defn.terminal; } Map resolveParams(final String pattern) { @@ -146,7 +151,8 @@ class CompositeParameterDefinition extends ParameterDefinition { @override Map resolveParams(String pattern) { final params = resolveParamsFromPath(template, pattern); - final definitions = [this, ...subparts].where((e) => e.descriptors.isNotEmpty); + final definitions = + [this, ...subparts].where((e) => e.descriptors.isNotEmpty); if (definitions.isNotEmpty) { for (final defn in definitions) { params[defn.name] = defn.descriptors.fold( diff --git a/packages/spanner/lib/src/parametric/descriptor.dart b/packages/spanner/lib/src/parametric/descriptor.dart index 9d0886c1..764012aa 100644 --- a/packages/spanner/lib/src/parametric/descriptor.dart +++ b/packages/spanner/lib/src/parametric/descriptor.dart @@ -3,7 +3,8 @@ import 'utils.dart'; typedef ParameterDescriptor = T Function(dynamic value); class SpannerRouteValidatorError extends ArgumentError { - SpannerRouteValidatorError(dynamic value, {String message = 'Invalid parameter value'}) + SpannerRouteValidatorError(dynamic value, + {String message = 'Invalid parameter value'}) : super.value(value, null, message); } diff --git a/packages/spanner/lib/src/route/action.dart b/packages/spanner/lib/src/route/action.dart index 5a3cb309..66cc94f4 100644 --- a/packages/spanner/lib/src/route/action.dart +++ b/packages/spanner/lib/src/route/action.dart @@ -18,7 +18,8 @@ mixin HandlerStore { void addRoute(HTTPMethod method, IndexedValue handler) { if (requestHandlers.containsKey(method)) { var name = (this as dynamic).name; - throw ArgumentError.value('${method.name}: $name', null, 'Route entry already exists'); + throw ArgumentError.value( + '${method.name}: $name', null, 'Route entry already exists'); } requestHandlers[method] = handler; diff --git a/packages/spanner/lib/src/tree/node.dart b/packages/spanner/lib/src/tree/node.dart index 39950e10..2b960fea 100644 --- a/packages/spanner/lib/src/tree/node.dart +++ b/packages/spanner/lib/src/tree/node.dart @@ -72,7 +72,8 @@ class ParametricNode extends Node { final List _definitions = []; - List get definitions => UnmodifiableListView(_definitions); + List get definitions => + UnmodifiableListView(_definitions); ParametricNode(ParameterDefinition defn) { _definitions.add(defn); @@ -81,7 +82,8 @@ class ParametricNode extends Node { bool get hasTerminal => _definitions.any((e) => e.terminal); void addNewDefinition(ParameterDefinition defn) { - final existing = _definitions.firstWhereOrNull((e) => e.isExactExceptName(defn)); + final existing = + _definitions.firstWhereOrNull((e) => e.isExactExceptName(defn)); if (existing != null) { if (existing.name != defn.name) { diff --git a/packages/spanner/test/case_insensitive_test.dart b/packages/spanner/test/case_insensitive_test.dart index 5736ff9a..7acdc35f 100644 --- a/packages/spanner/test/case_insensitive_test.dart +++ b/packages/spanner/test/case_insensitive_test.dart @@ -8,7 +8,8 @@ import 'helpers/test_utils.dart'; void main() { test('case insensitive static routes of level 1', () { final config = const RouterConfig(caseSensitive: false); - final router = Spanner(config: config)..addRoute(HTTPMethod.GET, '/woo', 'Hello World'); + final router = Spanner(config: config) + ..addRoute(HTTPMethod.GET, '/woo', 'Hello World'); final result = router.lookup(HTTPMethod.GET, '/woo'); expect(result, isStaticNode('woo')); @@ -17,7 +18,8 @@ void main() { test('case insensitive static routes of level 2', () { final config = const RouterConfig(caseSensitive: false); - final router = Spanner(config: config)..addRoute(HTTPMethod.GET, '/foo/woo', 'Foo Bar'); + final router = Spanner(config: config) + ..addRoute(HTTPMethod.GET, '/foo/woo', 'Foo Bar'); final result = router.lookup(HTTPMethod.GET, '/foo/woo'); expect(result, isStaticNode('woo')); @@ -26,7 +28,8 @@ void main() { test('case insensitive static routes of level 3', () { final config = const RouterConfig(caseSensitive: false); - final router = Spanner(config: config)..addRoute(HTTPMethod.GET, '/foo/bar/woo', 'foo bar'); + final router = Spanner(config: config) + ..addRoute(HTTPMethod.GET, '/foo/bar/woo', 'foo bar'); final result = router.lookup(HTTPMethod.GET, '/Foo/bAR/WoO'); expect(result, isStaticNode('woo')); @@ -35,7 +38,8 @@ void main() { test('parametric case insensitive', () { final config = const RouterConfig(caseSensitive: false); - final router = Spanner(config: config)..addRoute(HTTPMethod.GET, '/foo/', 'fam zing'); + final router = Spanner(config: config) + ..addRoute(HTTPMethod.GET, '/foo/', 'fam zing'); final result = router.lookup(HTTPMethod.GET, '/Foo/bAR'); expect(result, havingParameters({'param': 'bAR'})); @@ -44,7 +48,8 @@ void main() { test('parametric case insensitive with capital letter', () { final config = const RouterConfig(caseSensitive: false); - final router = Spanner(config: config)..addRoute(HTTPMethod.GET, '/foo/', 'on colos'); + final router = Spanner(config: config) + ..addRoute(HTTPMethod.GET, '/foo/', 'on colos'); final result = router.lookup(HTTPMethod.GET, '/Foo/bAR'); expect(result, havingParameters({'Param': 'bAR'})); @@ -53,14 +58,17 @@ void main() { test('case insensitive with capital letter in static path with param', () { final config = const RouterConfig(caseSensitive: false); - final router = Spanner(config: config)..addRoute(HTTPMethod.GET, '/Foo/bar/', 'merry c'); + final router = Spanner(config: config) + ..addRoute(HTTPMethod.GET, '/Foo/bar/', 'merry c'); final result = router.lookup(HTTPMethod.GET, '/foo/bar/baZ'); expect(result, havingParameters({'param': 'baZ'})); expect(result, hasValues(['merry c'])); }); - test('case insensitive with multiple paths containing capital letter in static path with param', () { + test( + 'case insensitive with multiple paths containing capital letter in static path with param', + () { final config = const RouterConfig(caseSensitive: false); final router = Spanner(config: config) ..addRoute(HTTPMethod.GET, '/Foo/bar/', null) @@ -78,7 +86,8 @@ void main() { test('case insensitive with multiple mixed-case params', () { final config = const RouterConfig(caseSensitive: false); - final router = Spanner(config: config)..addRoute(HTTPMethod.GET, '/foo//', null); + final router = Spanner(config: config) + ..addRoute(HTTPMethod.GET, '/foo//', null); expect( router.lookup(HTTPMethod.GET, '/FOO/My/bAR'), @@ -109,11 +118,14 @@ void main() { ); }); - test('case insensitive with multiple mixed-case params within same slash couple', () { + test( + 'case insensitive with multiple mixed-case params within same slash couple', + () { final config = const RouterConfig(caseSensitive: false); final router = Spanner(config: config) ..addRoute(HTTPMethod.GET, '/users/', null) - ..addRoute(HTTPMethod.GET, '/users/user-.png.dmg', 'kanzo') + ..addRoute( + HTTPMethod.GET, '/users/user-.png.dmg', 'kanzo') ..addRoute(HTTPMethod.GET, '/foo/-', null); var result = router.lookup(HTTPMethod.GET, '/FOO/My-bAR'); @@ -131,7 +143,8 @@ void main() { test('parametric case insensitive with a static part', () { final config = const RouterConfig(caseSensitive: false); - final router = Spanner(config: config)..addRoute(HTTPMethod.GET, '/foo/my-', null); + final router = Spanner(config: config) + ..addRoute(HTTPMethod.GET, '/foo/my-', null); expect( router.lookup(HTTPMethod.GET, '/Foo/MY-bAR'), diff --git a/packages/spookie/example/spookie_example.dart b/packages/spookie/example/spookie_example.dart index aef5864e..bfc8a477 100644 --- a/packages/spookie/example/spookie_example.dart +++ b/packages/spookie/example/spookie_example.dart @@ -7,7 +7,9 @@ void main() async { final app = Pharaoh(); app.get('/', (req, res) { - return res.type(ContentType.parse('application/vnd.example+json')).json({"hello": "world"}); + return res + .type(ContentType.parse('application/vnd.example+json')) + .json({"hello": "world"}); }); test('should not override previous Content-Types', () async { diff --git a/packages/spookie/lib/spookie.dart b/packages/spookie/lib/spookie.dart index 7bb1b85d..f3eae985 100644 --- a/packages/spookie/lib/spookie.dart +++ b/packages/spookie/lib/spookie.dart @@ -98,7 +98,8 @@ class _$SpookieImpl extends Spookie { Object? body, }) => expectHttp( - http.delete(getUri(path), headers: mergeHeaders(headers ?? {}), body: body), + http.delete(getUri(path), + headers: mergeHeaders(headers ?? {}), body: body), ); @override @@ -108,7 +109,8 @@ class _$SpookieImpl extends Spookie { Object? body, }) => expectHttp( - http.patch(getUri(path), headers: mergeHeaders(headers ?? {}), body: body), + http.patch(getUri(path), + headers: mergeHeaders(headers ?? {}), body: body), ); @override @@ -118,7 +120,8 @@ class _$SpookieImpl extends Spookie { Object? body, }) => expectHttp( - http.put(getUri(path), headers: mergeHeaders(headers ?? {}), body: body), + http.put(getUri(path), + headers: mergeHeaders(headers ?? {}), body: body), ); @override diff --git a/packages/spookie/lib/src/http_expectation.dart b/packages/spookie/lib/src/http_expectation.dart index 85899cef..a4b47e9c 100644 --- a/packages/spookie/lib/src/http_expectation.dart +++ b/packages/spookie/lib/src/http_expectation.dart @@ -8,25 +8,31 @@ import 'expectation.dart'; typedef HttpFutureResponse = Future; -HttpResponseExpection expectHttp(HttpFutureResponse value) => HttpResponseExpection(value); +HttpResponseExpection expectHttp(HttpFutureResponse value) => + HttpResponseExpection(value); typedef GetValueFromResponse = T Function(http.Response response); typedef MatchCase = ({GetValueFromResponse value, dynamic matcher}); -class HttpResponseExpection extends ExpectationBase { +class HttpResponseExpection + extends ExpectationBase { HttpResponseExpection(super.value); final List _matchcases = []; HttpResponseExpection expectHeader(String header, dynamic matcher) { - final MatchCase test = (value: (resp) => resp.headers[header], matcher: matcher); + final MatchCase test = + (value: (resp) => resp.headers[header], matcher: matcher); _matchcases.add(test); return this; } HttpResponseExpection expectContentType(dynamic matcher) { - final MatchCase test = (value: (resp) => resp.headers[HttpHeaders.contentTypeHeader], matcher: matcher); + final MatchCase test = ( + value: (resp) => resp.headers[HttpHeaders.contentTypeHeader], + matcher: matcher + ); _matchcases.add(test); return this; } @@ -56,10 +62,12 @@ class HttpResponseExpection extends ExpectationBase res.headers, - matcher: containsPair(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8') + matcher: containsPair( + HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8') )); - final MatchCase value = (value: (resp) => jsonDecode(resp.body), matcher: matcher); + final MatchCase value = + (value: (resp) => jsonDecode(resp.body), matcher: matcher); _matchcases.add(value); return this; } diff --git a/packages/spookie/test/spookie_test.dart b/packages/spookie/test/spookie_test.dart index 38a967db..ca49c9e4 100644 --- a/packages/spookie/test/spookie_test.dart +++ b/packages/spookie/test/spookie_test.dart @@ -9,21 +9,34 @@ void main() { group('when initialized', () { test('should fire up the app on an ephemeral port', () async { final app = Pharaoh()..get('/', (req, res) => res.send('Hello World')); - await (await (request(app))).get('/').expectStatus(200).expectBody('Hello World').test(); + await (await (request(app))) + .get('/') + .expectStatus(200) + .expectBody('Hello World') + .test(); }); test('should work with an active server', () async { - final app = Pharaoh()..post('/hello', (req, res) => res.ok('Hello World')); + final app = Pharaoh() + ..post('/hello', (req, res) => res.ok('Hello World')); await (await (request(app))).get('/').expectStatus(404).test(); - await (await (request(app))).post('/hello', {}).expectStatus(200).test(); + await (await (request(app))) + .post('/hello', {}) + .expectStatus(200) + .test(); }); test('should work with remote server', () async { - final app = Pharaoh()..put('/hello', (req, res) => res.ok('Hey Daddy Yo!')); + final app = Pharaoh() + ..put('/hello', (req, res) => res.ok('Hey Daddy Yo!')); await app.listen(port: 0); - await (await (request(app))).put('/hello').expectStatus(200).expectBody('Hey Daddy Yo!').test(); + await (await (request(app))) + .put('/hello') + .expectStatus(200) + .expectBody('Hey Daddy Yo!') + .test(); await app.shutdown(); }); @@ -31,7 +44,9 @@ void main() { group('when expectBody', () { test('should work with encoded value', () async { - final app = Pharaoh()..get('/', (req, res) => res.json({'firstname': 'Foo', 'lastname': 'Bar'})); + final app = Pharaoh() + ..get('/', + (req, res) => res.json({'firstname': 'Foo', 'lastname': 'Bar'})); await (await (request(app))) .get('/') @@ -41,7 +56,9 @@ void main() { }); test('should work with Map value', () async { - final app = Pharaoh()..get('/', (req, res) => res.json({'firstname': 'Foo', 'lastname': 'Bar'})); + final app = Pharaoh() + ..get('/', + (req, res) => res.json({'firstname': 'Foo', 'lastname': 'Bar'})); await (await (request(app))) .get('/') @@ -51,7 +68,9 @@ void main() { }); test('when expectBodyCustom', () async { - final app = Pharaoh()..get('/', (req, res) => res.json({'name': 'Chima', 'lastname': 'Bar'})); + final app = Pharaoh() + ..get( + '/', (req, res) => res.json({'name': 'Chima', 'lastname': 'Bar'})); await (await (request(app))) .get('/') @@ -61,7 +80,9 @@ void main() { }); test('when expectJsonBody', () async { - final app = Pharaoh()..get('/', (req, res) => res.json({'firstname': 'Foo', 'lastname': 'Bar'})); + final app = Pharaoh() + ..get('/', + (req, res) => res.json({'firstname': 'Foo', 'lastname': 'Bar'})); await (await (request(app))) .get('/') @@ -71,7 +92,11 @@ void main() { test('when expectHeaders', () async { final app = Pharaoh() - ..get('/', (req, res) => res.cookie('user', '204').json({'firstname': 'Foo', 'lastname': 'Bar'})); + ..get( + '/', + (req, res) => res + .cookie('user', '204') + .json({'firstname': 'Foo', 'lastname': 'Bar'})); await (await (request(app))) .get('/') @@ -79,14 +104,19 @@ void main() { .expectHeaders(allOf( containsPair(HttpHeaders.setCookieHeader, 'user=204; Path=/'), containsPair(HttpHeaders.contentLengthHeader, '36'), - containsPair(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8'), + containsPair(HttpHeaders.contentTypeHeader, + 'application/json; charset=utf-8'), )) .test(); }); test('when custom', () async { final app = Pharaoh() - ..get('/', (req, res) => res.cookie('user', '204').json({'firstname': 'Foo', 'lastname': 'Bar'})); + ..get( + '/', + (req, res) => res + .cookie('user', '204') + .json({'firstname': 'Foo', 'lastname': 'Bar'})); await (await (request(app))) .get('/') @@ -106,26 +136,42 @@ void main() { }); test('when expectStatus', () async { - final app = Pharaoh()..get('/', (req, res) => res.json('Pookie & Reyrey', statusCode: 500)); + final app = Pharaoh() + ..get('/', (req, res) => res.json('Pookie & Reyrey', statusCode: 500)); await (await (request(app))).get('/').expectStatus(500).test(); }); test('when expectHeader', () async { - final app = Pharaoh()..get('/', (req, res) => res.json('Pookie & Reyrey', statusCode: 500)); + final app = Pharaoh() + ..get('/', (req, res) => res.json('Pookie & Reyrey', statusCode: 500)); - await (await (request(app))).get('/').expectHeader(HttpHeaders.contentLengthHeader, '17').test(); + await (await (request(app))) + .get('/') + .expectHeader(HttpHeaders.contentLengthHeader, '17') + .test(); }); group('with methods', () { test('when auth', () async { - final app = Pharaoh()..get('/', (req, res) => res.ok(jsonEncode(req.headers[HttpHeaders.authorizationHeader]))); + final app = Pharaoh() + ..get( + '/', + (req, res) => res.ok( + jsonEncode(req.headers[HttpHeaders.authorizationHeader]))); - await (await (request(app))).auth('foo', 'bar').get('/').expectBody(['Basic Zm9vOmJhcg==']).test(); + await (await (request(app))) + .auth('foo', 'bar') + .get('/') + .expectBody(['Basic Zm9vOmJhcg==']).test(); }); test('when token', () async { - final app = Pharaoh()..get('/', (req, res) => res.ok(jsonEncode(req.headers[HttpHeaders.authorizationHeader]))); + final app = Pharaoh() + ..get( + '/', + (req, res) => res.ok( + jsonEncode(req.headers[HttpHeaders.authorizationHeader]))); await (await (request(app))) .token('#K#KKDJ#JJ#*8**##') @@ -135,7 +181,10 @@ void main() { test('when delete', () async { final app = Pharaoh() - ..delete('/', (req, res) => res.ok(jsonEncode(req.headers[HttpHeaders.authorizationHeader]))); + ..delete( + '/', + (req, res) => res.ok( + jsonEncode(req.headers[HttpHeaders.authorizationHeader]))); await (await (request(app))) .token('#K#KKDJ#JJ#*8**##') @@ -145,7 +194,10 @@ void main() { test('when patch', () async { final app = Pharaoh() - ..patch('/', (req, res) => res.ok(jsonEncode(req.headers[HttpHeaders.authorizationHeader]))); + ..patch( + '/', + (req, res) => res.ok( + jsonEncode(req.headers[HttpHeaders.authorizationHeader]))); await (await (request(app))) .token('#K#KKDJ#JJ#*8**##') diff --git a/pharaoh_examples/test/api_service_test.dart b/pharaoh_examples/test/api_service_test.dart index 6fa6e95c..ad92460c 100644 --- a/pharaoh_examples/test/api_service_test.dart +++ b/pharaoh_examples/test/api_service_test.dart @@ -14,7 +14,8 @@ void main() async { await (await request(apisvc.app)) .get('/api/users') .expectStatus(400) - .expectHeader(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8') + .expectHeader(HttpHeaders.contentTypeHeader, + 'application/json; charset=utf-8') .expectBody('"API key required"') .test(); }); @@ -23,7 +24,8 @@ void main() async { await (await request(apisvc.app)) .get('/api/users?api-key=asfas') .expectStatus(401) - .expectHeader(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8') + .expectHeader(HttpHeaders.contentTypeHeader, + 'application/json; charset=utf-8') .expectBody('"Invalid API key"') .test(); }); @@ -40,7 +42,8 @@ void main() async { await (await request(apisvc.app)) .get('/api/users?api-key=foo') .expectStatus(200) - .expectHeader(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8') + .expectHeader(HttpHeaders.contentTypeHeader, + 'application/json; charset=utf-8') .expectBody(result) .test(); }); @@ -55,7 +58,8 @@ void main() async { await (await request(apisvc.app)) .get('/api/user/tobi/repos?api-key=foo') .expectStatus(200) - .expectHeader(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8') + .expectHeader(HttpHeaders.contentTypeHeader, + 'application/json; charset=utf-8') .expectBody(result) .test(); }); @@ -64,7 +68,8 @@ void main() async { await (await request(apisvc.app)) .get('/api/user/chima/repos?api-key=foo') .expectStatus(404) - .expectHeader(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8') + .expectHeader(HttpHeaders.contentTypeHeader, + 'application/json; charset=utf-8') .expectBody({"error": "Not found"}).test(); }); }); @@ -79,7 +84,8 @@ void main() async { await (await request(apisvc.app)) .get('/api/repos?api-key=foo') .expectStatus(200) - .expectHeader(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8') + .expectHeader(HttpHeaders.contentTypeHeader, + 'application/json; charset=utf-8') .expectBody(result) .test(); });