From 1db6b35fb3f71e8acf9679405de3ae1344722a20 Mon Sep 17 00:00:00 2001 From: thomashii Date: Wed, 10 Mar 2021 00:40:38 +0800 Subject: [PATCH 001/171] Migrated pretty_logging to nndb --- packages/pretty_logging/lib/pretty_logging.dart | 14 +++++++------- packages/pretty_logging/pubspec.yaml | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/pretty_logging/lib/pretty_logging.dart b/packages/pretty_logging/lib/pretty_logging.dart index 30917c6b..bc45c81b 100644 --- a/packages/pretty_logging/lib/pretty_logging.dart +++ b/packages/pretty_logging/lib/pretty_logging.dart @@ -8,24 +8,24 @@ import 'package:io/ansi.dart'; /// /// You can also pass a custom [printFunction] or [logColorChooser]. void prettyLog(LogRecord record, - {bool Function(LogRecord) omitError, - void Function(String) printFunction, - AnsiCode Function(Level) logColorChooser}) { + {bool Function(LogRecord)? omitError, + void Function(String)? printFunction, + AnsiCode Function(Level)? logColorChooser}) { logColorChooser ??= chooseLogColor; omitError ??= (_) => false; printFunction ??= print; var code = logColorChooser(record.level); - if (record.error == null) printFunction(code.wrap(record.toString())); + if (record.error == null) printFunction(code.wrap(record.toString())!); if (record.error != null) { var err = record.error; if (omitError(record)) return; - printFunction(code.wrap(record.toString() + '\n')); - printFunction(code.wrap(err.toString())); + printFunction(code.wrap(record.toString() + '\n')!); + printFunction(code.wrap(err.toString())!); if (record.stackTrace != null) { - printFunction(code.wrap(record.stackTrace.toString())); + printFunction(code.wrap(record.stackTrace.toString())!); } } } diff --git a/packages/pretty_logging/pubspec.yaml b/packages/pretty_logging/pubspec.yaml index 6a3f7a16..5f693722 100644 --- a/packages/pretty_logging/pubspec.yaml +++ b/packages/pretty_logging/pubspec.yaml @@ -1,10 +1,10 @@ name: pretty_logging -version: 2.0.0 +version: 2.1.0 description: Standalone helper for colorful logging output, using pkg:io AnsiCode. author: Tobe Osakwe homepage: https://github.com/angel-dart/pretty_logging environment: - sdk: ">=2.10.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: io: ^1.0.0 logging: ^1.0.0 From 5920212d04f569e240b47645433dce612b3a1d0d Mon Sep 17 00:00:00 2001 From: thomashii Date: Wed, 10 Mar 2021 00:41:42 +0800 Subject: [PATCH 002/171] Migrated angel_http_exception to nnbd --- packages/http_exception/lib/angel_http_exception.dart | 8 ++++---- packages/http_exception/pubspec.yaml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/http_exception/lib/angel_http_exception.dart b/packages/http_exception/lib/angel_http_exception.dart index f15ebafc..b38d854e 100644 --- a/packages/http_exception/lib/angel_http_exception.dart +++ b/packages/http_exception/lib/angel_http_exception.dart @@ -15,13 +15,13 @@ class AngelHttpException implements Exception { final List errors = []; /// The cause of this exception. - String message; + String? message; /// The [StackTrace] associated with this error. - StackTrace stackTrace; + StackTrace? stackTrace; /// An HTTP status code this exception will throw. - int statusCode; + int? statusCode; AngelHttpException(this.error, {this.message = '500 Internal Server Error', @@ -52,7 +52,7 @@ class AngelHttpException implements Exception { factory AngelHttpException.fromMap(Map data) { return AngelHttpException( null, - statusCode: (data['status_code'] ?? data['statusCode']) as int, + statusCode: (data['status_code'] ?? data['statusCode']) as int?, message: data['message']?.toString(), errors: data['errors'] is Iterable ? ((data['errors'] as Iterable).map((x) => x.toString()).toList()) diff --git a/packages/http_exception/pubspec.yaml b/packages/http_exception/pubspec.yaml index 6a4de47a..ea88a684 100644 --- a/packages/http_exception/pubspec.yaml +++ b/packages/http_exception/pubspec.yaml @@ -1,9 +1,9 @@ name: angel_http_exception -version: 2.0.0 +version: 2.1.0 description: Exception class that can be serialized to JSON and serialized to clients. #author: Tobe O homepage: https://github.com/dukefirehawk/angel/packages/http_exception environment: - sdk: ">=2.10.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dev_dependencies: pedantic: ^1.11.0 \ No newline at end of file From 5b6f6bdf975207d58c1c56ed18b77c8f32bdb0f0 Mon Sep 17 00:00:00 2001 From: thomashii Date: Tue, 16 Mar 2021 08:14:28 +0800 Subject: [PATCH 003/171] Added package code_buffer --- CHANGELOG.md | 9 +- packages/code_buffer/.gitignore | 85 ++++++++ packages/code_buffer/.travis.yml | 1 + packages/code_buffer/CHANGELOG.md | 2 + packages/code_buffer/LICENSE | 21 ++ packages/code_buffer/README.md | 63 ++++++ packages/code_buffer/analysis_options.yaml | 3 + packages/code_buffer/example/main.dart | 45 ++++ packages/code_buffer/lib/code_buffer.dart | 229 +++++++++++++++++++++ packages/code_buffer/pubspec.yaml | 12 ++ packages/code_buffer/test/copy_test.dart | 45 ++++ packages/code_buffer/test/span_test.dart | 44 ++++ packages/code_buffer/test/write_test.dart | 87 ++++++++ packages/http_exception/pubspec.yaml | 2 +- packages/pretty_logging/pubspec.yaml | 6 +- 15 files changed, 646 insertions(+), 8 deletions(-) create mode 100644 packages/code_buffer/.gitignore create mode 100644 packages/code_buffer/.travis.yml create mode 100644 packages/code_buffer/CHANGELOG.md create mode 100644 packages/code_buffer/LICENSE create mode 100644 packages/code_buffer/README.md create mode 100644 packages/code_buffer/analysis_options.yaml create mode 100644 packages/code_buffer/example/main.dart create mode 100644 packages/code_buffer/lib/code_buffer.dart create mode 100644 packages/code_buffer/pubspec.yaml create mode 100644 packages/code_buffer/test/copy_test.dart create mode 100644 packages/code_buffer/test/span_test.dart create mode 100644 packages/code_buffer/test/write_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index c8746a60..d02adae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ -# 3.0.1 (NNBD) +# 4.0.0 (NNBD) * Changed Dart SDK requirements for all packages to ">=2.12.0 <3.0.0" to support NNBD. -* Updated pretty_logging to 2.0.0 -* Updated angel_http_exception to 2.0.0 -* Updated angel_cli to 3.0.0. (Rename not working) +* Updated pretty_logging to 3.0.0 +* Updated angel_http_exception to 3.0.0 +* Moved to https://github.com/dukefirehawk/cli +* Updated angel_route to 5.0.0 # 3.0.0 (Non NNBD) * Changed Dart SDK requirements for all packages to ">=2.10.0 <3.0.0" diff --git a/packages/code_buffer/.gitignore b/packages/code_buffer/.gitignore new file mode 100644 index 00000000..0e12a575 --- /dev/null +++ b/packages/code_buffer/.gitignore @@ -0,0 +1,85 @@ +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.packages +.pub/ +build/ +# If you're building an application, you may want to check-in your pubspec.lock +pubspec.lock + +# Directory created by dartdoc +# If you don't generate documentation locally you can remove this line. +doc/api/ +### Dart template +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub + +# SDK 1.20 and later (no longer creates packages directories) + +# Older SDK versions +# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20) +.project +.buildlog +**/packages/ + + +# Files created by dart2js +# (Most Dart developers will use pub build to compile Dart, use/modify these +# rules if you intend to use dart2js directly +# Convention is to use extension '.dart.js' for Dart compiled to Javascript to +# differentiate from explicit Javascript files) +*.dart.js +*.part.js +*.js.deps +*.js.map +*.info.json + +# Directory created by dartdoc + +# Don't commit pubspec lock file +# (Library packages only! Remove pattern if developing an application package) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties diff --git a/packages/code_buffer/.travis.yml b/packages/code_buffer/.travis.yml new file mode 100644 index 00000000..de2210c9 --- /dev/null +++ b/packages/code_buffer/.travis.yml @@ -0,0 +1 @@ +language: dart \ No newline at end of file diff --git a/packages/code_buffer/CHANGELOG.md b/packages/code_buffer/CHANGELOG.md new file mode 100644 index 00000000..a9c6ebb1 --- /dev/null +++ b/packages/code_buffer/CHANGELOG.md @@ -0,0 +1,2 @@ +# 1.0.1 +* Added `CodeBuffer.noWhitespace()`. \ No newline at end of file diff --git a/packages/code_buffer/LICENSE b/packages/code_buffer/LICENSE new file mode 100644 index 00000000..3de28325 --- /dev/null +++ b/packages/code_buffer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Tobe O + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/code_buffer/README.md b/packages/code_buffer/README.md new file mode 100644 index 00000000..d556c428 --- /dev/null +++ b/packages/code_buffer/README.md @@ -0,0 +1,63 @@ +# code_buffer +[![Pub](https://img.shields.io/pub/v/code_buffer.svg)](https://pub.dartlang.org/packages/code_buffer) +[![build status](https://travis-ci.org/thosakwe/code_buffer.svg)](https://travis-ci.org/thosakwe/code_buffer) + +An advanced StringBuffer geared toward generating code, and source maps. + +# Installation +In your `pubspec.yaml`: + +```yaml +dependencies: + code_buffer: ^1.0.0 +``` + +# Usage +Use a `CodeBuffer` just like any regular `StringBuffer`: + +```dart +String someFunc() { + var buf = new CodeBuffer(); + buf + ..write('hello ') + ..writeln('world!'); + return buf.toString(); +} +``` + +However, a `CodeBuffer` supports indentation. + +```dart +void someOtherFunc() { + var buf = new CodeBuffer(); + // Custom options... + var buf = new CodeBuffer(newline: '\r\n', space: '\t', trailingNewline: true); + + // Any following lines will have an incremented indentation level... + buf.indent(); + + // And vice-versa: + buf.outdent(); +} +``` + +`CodeBuffer` instances keep track of every `SourceSpan` they create. +This makes them useful for codegen tools, or to-JS compilers. + +```dart +void someFunc(CodeBuffer buf) { + buf.write('hello'); + expect(buf.lastLine.text, 'hello'); + + buf.writeln('world'); + expect(buf.lastLine.lastSpan.start.column, 5); +} +``` + +You can copy a `CodeBuffer` into another, heeding indentation rules: + +```dart +void yetAnotherFunc(CodeBuffer a, CodeBuffer b) { + b.copyInto(a); +} +``` \ No newline at end of file diff --git a/packages/code_buffer/analysis_options.yaml b/packages/code_buffer/analysis_options.yaml new file mode 100644 index 00000000..eae1e42a --- /dev/null +++ b/packages/code_buffer/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + strong-mode: + implicit-casts: false \ No newline at end of file diff --git a/packages/code_buffer/example/main.dart b/packages/code_buffer/example/main.dart new file mode 100644 index 00000000..c02b9674 --- /dev/null +++ b/packages/code_buffer/example/main.dart @@ -0,0 +1,45 @@ +import 'package:code_buffer/code_buffer.dart'; +import 'package:test/test.dart'; + +/// Use a `CodeBuffer` just like any regular `StringBuffer`: +String someFunc() { + var buf = new CodeBuffer(); + buf + ..write('hello ') + ..writeln('world!'); + return buf.toString(); +} + +/// However, a `CodeBuffer` supports indentation. +void someOtherFunc() { + var buf = new CodeBuffer(); + + // Custom options... + // ignore: unused_local_variable + var customBuf = new CodeBuffer(newline: '\r\n', space: '\t', trailingNewline: true); + + // Without whitespace.. + // ignore: unused_local_variable + var minifyingBuf = new CodeBuffer.noWhitespace(); + + // Any following lines will have an incremented indentation level... + buf.indent(); + + // And vice-versa: + buf.outdent(); +} + +/// `CodeBuffer` instances keep track of every `SourceSpan` they create. +//This makes them useful for codegen tools, or to-JS compilers. +void yetAnotherOtherFunc(CodeBuffer buf) { + buf.write('hello'); + expect(buf.lastLine!.text, 'hello'); + + buf.writeln('world'); + expect(buf.lastLine!.lastSpan!.start.column, 5); +} + +/// You can copy a `CodeBuffer` into another, heeding indentation rules: +void yetEvenAnotherFunc(CodeBuffer a, CodeBuffer b) { + b.copyInto(a); +} \ No newline at end of file diff --git a/packages/code_buffer/lib/code_buffer.dart b/packages/code_buffer/lib/code_buffer.dart new file mode 100644 index 00000000..68406bdc --- /dev/null +++ b/packages/code_buffer/lib/code_buffer.dart @@ -0,0 +1,229 @@ +import 'package:source_span/source_span.dart'; + +/// An advanced StringBuffer geared toward generating code, and source maps. +class CodeBuffer implements StringBuffer { + /// The character sequence used to represent a line break. + final String newline; + + /// The character sequence used to represent a space/tab. + final String space; + + /// The source URL to be applied to all generated [SourceSpan] instances. + final sourceUrl; + + /// If `true` (default: `false`), then an additional [newline] will be inserted at the end of the generated string. + final bool trailingNewline; + + final List _lines = []; + CodeBufferLine? _currentLine, _lastLine; + int _indentationLevel = 0; + int _length = 0; + + CodeBuffer( + {this.space: ' ', + this.newline: '\n', + this.trailingNewline: false, + this.sourceUrl}); + + /// Creates a [CodeBuffer] that does not emit additional whitespace. + factory CodeBuffer.noWhitespace({sourceUrl}) => CodeBuffer( + space: '', newline: '', trailingNewline: false, sourceUrl: sourceUrl); + + /// The last line created within this buffer. + CodeBufferLine? get lastLine => _lastLine; + + /// Returns an immutable collection of the [CodeBufferLine]s within this instance. + List get lines => List.unmodifiable(_lines); + + @override + bool get isEmpty => _lines.isEmpty; + + @override + bool get isNotEmpty => _lines.isNotEmpty; + + @override + int get length => _length; + + CodeBufferLine _createLine() { + var start = SourceLocation( + _length, + sourceUrl: sourceUrl, + line: _lines.length, + column: _indentationLevel * space.length, + ); + var line = CodeBufferLine._(_indentationLevel, start).._end = start; + _lines.add(_lastLine = line); + return line; + } + + /// Increments the indentation level. + void indent() { + _indentationLevel++; + } + + /// Decrements the indentation level, if it is greater than `0`. + void outdent() { + if (_indentationLevel > 0) _indentationLevel--; + } + + /// Copies the contents of this [CodeBuffer] into another, preserving indentation and source mapping information. + void copyInto(CodeBuffer other) { + if (_lines.isEmpty) return; + int i = 0; + + for (var line in _lines) { + // To compute offset: + // 1. Find current length of other + // 2. Add length of its newline + // 3. Add indentation + var column = (other._indentationLevel + line.indentationLevel) * + other.space.length; + var offset = other._length + other.newline.length + column; + + // Re-compute start + end + var start = SourceLocation( + offset, + sourceUrl: other.sourceUrl, + line: other._lines.length + i, + column: column, + ); + + var end = SourceLocation( + offset + line.span.length, + sourceUrl: other.sourceUrl, + line: start.line, + column: column + line._buf.length, + ); + + var clone = CodeBufferLine._( + line.indentationLevel + other._indentationLevel, start) + .._end = end + .._buf.write(line._buf.toString()); + + // Adjust lastSpan + if (line._lastSpan != null) { + var s = line._lastSpan!.start; + var lastSpanColumn = + ((line.indentationLevel + other._indentationLevel) * + other.space.length) + + line.text.indexOf(line._lastSpan!.text); + clone._lastSpan = SourceSpan( + SourceLocation( + offset + s.offset, + sourceUrl: other.sourceUrl, + line: clone.span.start.line, + column: lastSpanColumn, + ), + SourceLocation( + offset + s.offset + line._lastSpan!.length, + sourceUrl: other.sourceUrl, + line: clone.span.end.line, + column: lastSpanColumn + line._lastSpan!.length, + ), + line._lastSpan!.text, + ); + } + + other._lines.add(other._currentLine = other._lastLine = clone); + + // Adjust length accordingly... + other._length = offset + clone.span.length; + i++; + } + + other.writeln(); + } + + @override + void clear() { + _lines.clear(); + _length = _indentationLevel = 0; + _currentLine = null; + } + + @override + void writeCharCode(int charCode) { + _currentLine ??= _createLine(); + + _currentLine!._buf.writeCharCode(charCode); + var end = _currentLine!._end; + _currentLine!._end = SourceLocation( + end.offset + 1, + sourceUrl: end.sourceUrl, + line: end.line, + column: end.column + 1, + ); + _length++; + _currentLine!._lastSpan = + SourceSpan(end, _currentLine!._end, String.fromCharCode(charCode)); + } + + @override + void write(Object? obj) { + var msg = obj.toString(); + _currentLine ??= _createLine(); + _currentLine!._buf.write(msg); + var end = _currentLine!._end; + _currentLine!._end = SourceLocation( + end.offset + msg.length, + sourceUrl: end.sourceUrl, + line: end.line, + column: end.column + msg.length, + ); + _length += msg.length; + _currentLine!._lastSpan = SourceSpan(end, _currentLine!._end, msg); + } + + @override + void writeln([Object? obj = ""]) { + if (obj != null && obj != '') write(obj); + _currentLine = null; + _length++; + } + + @override + void writeAll(Iterable objects, [String separator = ""]) { + write(objects.join(separator)); + } + + @override + String toString() { + var buf = StringBuffer(); + int i = 0; + + for (var line in lines) { + if (i++ > 0) buf.write(newline); + for (int j = 0; j < line.indentationLevel; j++) buf.write(space); + buf.write(line._buf.toString()); + } + + if (trailingNewline == true) buf.write(newline); + + return buf.toString(); + } +} + +/// Represents a line of text within a [CodeBuffer]. +class CodeBufferLine { + /// Mappings from one [SourceSpan] to another, to aid with generating dynamic source maps. + final Map sourceMappings = {}; + + /// The level of indentation preceding this line. + final int indentationLevel; + + final SourceLocation _start; + final StringBuffer _buf = StringBuffer(); + late SourceLocation _end; + SourceSpan? _lastSpan; + + CodeBufferLine._(this.indentationLevel, this._start); + + /// The [SourceSpan] corresponding to the last text written to this line. + SourceSpan? get lastSpan => _lastSpan; + + /// The [SourceSpan] corresponding to this entire line. + SourceSpan get span => SourceSpan(_start, _end, _buf.toString()); + + /// The text within this line. + String get text => _buf.toString(); +} diff --git a/packages/code_buffer/pubspec.yaml b/packages/code_buffer/pubspec.yaml new file mode 100644 index 00000000..ae4fa8e9 --- /dev/null +++ b/packages/code_buffer/pubspec.yaml @@ -0,0 +1,12 @@ +name: code_buffer +version: 2.0.0 +description: An advanced StringBuffer geared toward generating code, and source maps. +author: Tobe O +homepage: https://github.com/thosakwe/code_buffer +environment: + sdk: '>=2.12.0 <3.0.0' +dependencies: + charcode: ^1.2.0 + source_span: ^1.8.1 +dev_dependencies: + test: ^1.16.8 \ No newline at end of file diff --git a/packages/code_buffer/test/copy_test.dart b/packages/code_buffer/test/copy_test.dart new file mode 100644 index 00000000..9d0170f0 --- /dev/null +++ b/packages/code_buffer/test/copy_test.dart @@ -0,0 +1,45 @@ +import 'package:code_buffer/code_buffer.dart'; +import 'package:test/test.dart'; + +main() { + var a = new CodeBuffer(), b = new CodeBuffer(); + + setUp(() { + a.writeln('outer block 1'); + b..writeln('inner block 1')..writeln('inner block 2'); + b.copyInto(a..indent()); + a + ..outdent() + ..writeln('outer block 2'); + }); + + tearDown(() { + a.clear(); + b.clear(); + }); + + test('sets correct text', () { + expect( + a.toString(), + [ + 'outer block 1', + ' inner block 1', + ' inner block 2', + 'outer block 2', + ].join('\n')); + }); + + test('sets lastLine+lastSpan', () { + var c = new CodeBuffer() + ..indent() + ..write('>') + ..writeln('innermost'); + c.copyInto(a); + expect(a.lastLine!.text, '>innermost'); + expect(a.lastLine!.span.start.column, 2); + expect(a.lastLine!.lastSpan!.start.line, 4); + expect(a.lastLine!.lastSpan!.start.column, 3); + expect(a.lastLine!.lastSpan!.end.line, 4); + expect(a.lastLine!.lastSpan!.end.column, 12); + }); +} diff --git a/packages/code_buffer/test/span_test.dart b/packages/code_buffer/test/span_test.dart new file mode 100644 index 00000000..c96acffd --- /dev/null +++ b/packages/code_buffer/test/span_test.dart @@ -0,0 +1,44 @@ +import 'package:charcode/charcode.dart'; +import 'package:code_buffer/code_buffer.dart'; +import 'package:test/test.dart'; + +main() { + var buf = new CodeBuffer(); + tearDown(buf.clear); + + test('writeCharCode', () { + buf.writeCharCode($x); + expect(buf.lastLine!.lastSpan!.start.column, 0); + expect(buf.lastLine!.lastSpan!.start.line, 0); + expect(buf.lastLine!.lastSpan!.end.column, 1); + expect(buf.lastLine!.lastSpan!.end.line, 0); + }); + + test('write', () { + buf.write('foo'); + expect(buf.lastLine!.lastSpan!.start.column, 0); + expect(buf.lastLine!.lastSpan!.start.line, 0); + expect(buf.lastLine!.lastSpan!.end.column, 3); + expect(buf.lastLine!.lastSpan!.end.line, 0); + }); + + test('multiple writes in one line', () { + buf..write('foo')..write('baz'); + expect(buf.lastLine!.lastSpan!.start.column, 3); + expect(buf.lastLine!.lastSpan!.start.line, 0); + expect(buf.lastLine!.lastSpan!.end.column, 6); + expect(buf.lastLine!.lastSpan!.end.line, 0); + }); + + test('multiple lines', () { + buf + ..writeln('foo') + ..write('bar') + ..write('+') + ..writeln('baz'); + expect(buf.lastLine!.lastSpan!.start.column, 4); + expect(buf.lastLine!.lastSpan!.start.line, 1); + expect(buf.lastLine!.lastSpan!.end.column, 7); + expect(buf.lastLine!.lastSpan!.end.line, 1); + }); +} diff --git a/packages/code_buffer/test/write_test.dart b/packages/code_buffer/test/write_test.dart new file mode 100644 index 00000000..31238980 --- /dev/null +++ b/packages/code_buffer/test/write_test.dart @@ -0,0 +1,87 @@ +import 'package:charcode/charcode.dart'; +import 'package:code_buffer/code_buffer.dart'; +import 'package:test/test.dart'; + +main() { + var buf = new CodeBuffer(); + tearDown(buf.clear); + + test('writeCharCode', () { + buf.writeCharCode($x); + expect(buf.toString(), 'x'); + }); + + test('write', () { + buf.write('hello world'); + expect(buf.toString(), 'hello world'); + }); + + test('custom space', () { + var b = new CodeBuffer(space: '+') + ..writeln('foo') + ..indent() + ..writeln('baz'); + expect(b.toString(), 'foo\n+baz'); + }); + + test('custom newline', () { + var b = new CodeBuffer(newline: 'N') + ..writeln('foo') + ..indent() + ..writeln('baz'); + expect(b.toString(), 'fooN baz'); + }); + + test('trailing newline', () { + var b = new CodeBuffer(trailingNewline: true)..writeln('foo'); + expect(b.toString(), 'foo\n'); + }); + + group('multiple lines', () { + setUp(() { + buf..writeln('foo')..writeln('bar')..writeln('baz'); + expect(buf.lines, hasLength(3)); + expect(buf.lines[0].text, 'foo'); + expect(buf.lines[1].text, 'bar'); + expect(buf.lines[2].text, 'baz'); + }); + }); + + test('indent', () { + buf + ..writeln('foo') + ..indent() + ..writeln('bar') + ..indent() + ..writeln('baz') + ..outdent() + ..writeln('quux') + ..outdent() + ..writeln('end'); + expect(buf.toString(), 'foo\n bar\n baz\n quux\nend'); + }); + + group('sets lastLine text', () { + test('writeCharCode', () { + buf.writeCharCode($x); + expect(buf.lastLine!.text, 'x'); + }); + + test('write', () { + buf.write('hello world'); + expect(buf.lastLine!.text, 'hello world'); + }); + }); + + group('sets lastLine lastSpan', () { + test('writeCharCode', () { + buf.writeCharCode($x); + expect(buf.lastLine!.lastSpan!.text, 'x'); + }); + + test('write', () { + buf.write('hello world'); + expect(buf.lastLine!.lastSpan!.text, 'hello world'); + }); + }); +} diff --git a/packages/http_exception/pubspec.yaml b/packages/http_exception/pubspec.yaml index ea88a684..a75358bb 100644 --- a/packages/http_exception/pubspec.yaml +++ b/packages/http_exception/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_http_exception -version: 2.1.0 +version: 3.0.0 description: Exception class that can be serialized to JSON and serialized to clients. #author: Tobe O homepage: https://github.com/dukefirehawk/angel/packages/http_exception diff --git a/packages/pretty_logging/pubspec.yaml b/packages/pretty_logging/pubspec.yaml index 5f693722..96a714f5 100644 --- a/packages/pretty_logging/pubspec.yaml +++ b/packages/pretty_logging/pubspec.yaml @@ -1,8 +1,8 @@ name: pretty_logging -version: 2.1.0 +version: 4.0.0 description: Standalone helper for colorful logging output, using pkg:io AnsiCode. -author: Tobe Osakwe -homepage: https://github.com/angel-dart/pretty_logging +#author: Tobe Osakwe +homepage: https://github.com/dukefirehawk/angel environment: sdk: '>=2.12.0 <3.0.0' dependencies: From 55beadd73712fea329862c9dfda40e596cf098be Mon Sep 17 00:00:00 2001 From: thomashii Date: Thu, 18 Mar 2021 07:04:36 +0800 Subject: [PATCH 004/171] Added combinator --- CHANGELOG.md | 4 +- packages/combinator/.gitignore | 66 +++ packages/combinator/.travis.yml | 4 + packages/combinator/CHANGELOG.md | 11 + packages/combinator/LICENSE | 21 + packages/combinator/README.md | 120 ++++++ packages/combinator/analysis_options.yaml | 4 + packages/combinator/combinator.iml | 14 + packages/combinator/example/basic_auth.dart | 55 +++ packages/combinator/example/calculator.dart | 70 ++++ packages/combinator/example/delimiter.dart | 28 ++ packages/combinator/example/json.dart | 70 ++++ packages/combinator/example/main.dart | 37 ++ packages/combinator/example/query_string.dart | 44 ++ packages/combinator/example/sexp.dart | 84 ++++ packages/combinator/example/tuple.dart | 14 + packages/combinator/lib/combinator.dart | 2 + .../lib/src/combinator/advance.dart | 26 ++ .../combinator/lib/src/combinator/any.dart | 85 ++++ .../combinator/lib/src/combinator/cache.dart | 26 ++ .../combinator/lib/src/combinator/cast.dart | 63 +++ .../combinator/lib/src/combinator/chain.dart | 105 +++++ .../combinator/lib/src/combinator/check.dart | 42 ++ .../lib/src/combinator/combinator.dart | 381 ++++++++++++++++++ .../lib/src/combinator/compare.dart | 38 ++ .../lib/src/combinator/fold_errors.dart | 29 ++ .../combinator/lib/src/combinator/index.dart | 38 ++ .../lib/src/combinator/longest.dart | 115 ++++++ .../combinator/lib/src/combinator/map.dart | 56 +++ .../combinator/lib/src/combinator/match.dart | 40 ++ .../lib/src/combinator/max_depth.dart | 28 ++ .../combinator/lib/src/combinator/negate.dart | 51 +++ .../combinator/lib/src/combinator/opt.dart | 57 +++ .../lib/src/combinator/recursion.dart | 142 +++++++ .../combinator/lib/src/combinator/reduce.dart | 45 +++ .../lib/src/combinator/reference.dart | 38 ++ .../combinator/lib/src/combinator/repeat.dart | 84 ++++ .../combinator/lib/src/combinator/safe.dart | 51 +++ .../lib/src/combinator/to_list.dart | 37 ++ .../combinator/lib/src/combinator/util.dart | 57 +++ .../combinator/lib/src/combinator/value.dart | 25 ++ packages/combinator/lib/src/error.dart | 23 ++ packages/combinator/pubspec.yaml | 20 + packages/combinator/test/all.dart | 12 + packages/combinator/test/common.dart | 3 + packages/combinator/test/list_test.dart | 22 + packages/combinator/test/match_test.dart | 16 + packages/combinator/test/misc_test.dart | 66 +++ packages/combinator/test/recursion_test.dart | 57 +++ packages/combinator/test/value_test.dart | 15 + 50 files changed, 2540 insertions(+), 1 deletion(-) create mode 100644 packages/combinator/.gitignore create mode 100644 packages/combinator/.travis.yml create mode 100644 packages/combinator/CHANGELOG.md create mode 100644 packages/combinator/LICENSE create mode 100644 packages/combinator/README.md create mode 100644 packages/combinator/analysis_options.yaml create mode 100644 packages/combinator/combinator.iml create mode 100644 packages/combinator/example/basic_auth.dart create mode 100644 packages/combinator/example/calculator.dart create mode 100644 packages/combinator/example/delimiter.dart create mode 100644 packages/combinator/example/json.dart create mode 100644 packages/combinator/example/main.dart create mode 100644 packages/combinator/example/query_string.dart create mode 100644 packages/combinator/example/sexp.dart create mode 100644 packages/combinator/example/tuple.dart create mode 100644 packages/combinator/lib/combinator.dart create mode 100644 packages/combinator/lib/src/combinator/advance.dart create mode 100644 packages/combinator/lib/src/combinator/any.dart create mode 100644 packages/combinator/lib/src/combinator/cache.dart create mode 100644 packages/combinator/lib/src/combinator/cast.dart create mode 100644 packages/combinator/lib/src/combinator/chain.dart create mode 100644 packages/combinator/lib/src/combinator/check.dart create mode 100644 packages/combinator/lib/src/combinator/combinator.dart create mode 100644 packages/combinator/lib/src/combinator/compare.dart create mode 100644 packages/combinator/lib/src/combinator/fold_errors.dart create mode 100644 packages/combinator/lib/src/combinator/index.dart create mode 100644 packages/combinator/lib/src/combinator/longest.dart create mode 100644 packages/combinator/lib/src/combinator/map.dart create mode 100644 packages/combinator/lib/src/combinator/match.dart create mode 100644 packages/combinator/lib/src/combinator/max_depth.dart create mode 100644 packages/combinator/lib/src/combinator/negate.dart create mode 100644 packages/combinator/lib/src/combinator/opt.dart create mode 100644 packages/combinator/lib/src/combinator/recursion.dart create mode 100644 packages/combinator/lib/src/combinator/reduce.dart create mode 100644 packages/combinator/lib/src/combinator/reference.dart create mode 100644 packages/combinator/lib/src/combinator/repeat.dart create mode 100644 packages/combinator/lib/src/combinator/safe.dart create mode 100644 packages/combinator/lib/src/combinator/to_list.dart create mode 100644 packages/combinator/lib/src/combinator/util.dart create mode 100644 packages/combinator/lib/src/combinator/value.dart create mode 100644 packages/combinator/lib/src/error.dart create mode 100644 packages/combinator/pubspec.yaml create mode 100644 packages/combinator/test/all.dart create mode 100644 packages/combinator/test/common.dart create mode 100644 packages/combinator/test/list_test.dart create mode 100644 packages/combinator/test/match_test.dart create mode 100644 packages/combinator/test/misc_test.dart create mode 100644 packages/combinator/test/recursion_test.dart create mode 100644 packages/combinator/test/value_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index d02adae7..abf107ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ * Changed Dart SDK requirements for all packages to ">=2.12.0 <3.0.0" to support NNBD. * Updated pretty_logging to 3.0.0 * Updated angel_http_exception to 3.0.0 -* Moved to https://github.com/dukefirehawk/cli +* Moved angel_cli to https://github.com/dukefirehawk/cli +* Added code_buffer 2.0.0 +* Added combinator 2.0.0 * Updated angel_route to 5.0.0 # 3.0.0 (Non NNBD) diff --git a/packages/combinator/.gitignore b/packages/combinator/.gitignore new file mode 100644 index 00000000..f277009e --- /dev/null +++ b/packages/combinator/.gitignore @@ -0,0 +1,66 @@ +# Created by .ignore support plugin (hsz.mobi) +### Dart template +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.packages +.pub/ +build/ +# If you're building an application, you may want to check-in your pubspec.lock +pubspec.lock + +# Directory created by dartdoc +# If you don't generate documentation locally you can remove this line. +doc/api/ +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +.dart_tool \ No newline at end of file diff --git a/packages/combinator/.travis.yml b/packages/combinator/.travis.yml new file mode 100644 index 00000000..2f22c5ce --- /dev/null +++ b/packages/combinator/.travis.yml @@ -0,0 +1,4 @@ +language: dart +dart: + - stable + - dev \ No newline at end of file diff --git a/packages/combinator/CHANGELOG.md b/packages/combinator/CHANGELOG.md new file mode 100644 index 00000000..01d35594 --- /dev/null +++ b/packages/combinator/CHANGELOG.md @@ -0,0 +1,11 @@ +# 1.1.0 +* Add `tupleX` parsers. Hooray for strong typing! + +# 1.0.0+3 +* `then` now *always* returns `dynamic`. + +# 1.0.0+2 +* `star` now includes with a call to `opt`. +* Added comments. +* Enforce generics on `separatedBy`. +* Enforce Dart 2 semantics. \ No newline at end of file diff --git a/packages/combinator/LICENSE b/packages/combinator/LICENSE new file mode 100644 index 00000000..3de28325 --- /dev/null +++ b/packages/combinator/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Tobe O + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/combinator/README.md b/packages/combinator/README.md new file mode 100644 index 00000000..63cd48e6 --- /dev/null +++ b/packages/combinator/README.md @@ -0,0 +1,120 @@ +# combinator +[![version](https://img.shields.io/pub/v/combinator.svg)](https://pub.dartlang.org/packages/combinator) +[![build status](https://travis-ci.org/thosakwe/combinator.svg)](https://travis-ci.org/thosakwe/combinator) + +Packrat parser combinators that support static typing, generics, file spans, memoization, and more. + +**RECOMMENDED:** +Check `example/` for examples. +The examples contain examples of using: +* Generic typing +* Reading `FileSpan` from `ParseResult` +* More... + +## Basic Usage +```dart +void main() { + // Parse a Pattern (usually String or RegExp). + var foo = match('foo'); + var number = match(new RegExp(r'[0-9]+'), errorMessage: 'Expected a number.'); + + // Set a value. + var numWithValue = number.map((r) => int.parse(r.span.text)); + + // Expect a pattern, or nothing. + var optional = numWithValue.opt(); + + // Expect a pattern zero or more times. + var star = optional.star(); + + // Expect one or more times. + var plus = optional.plus(); + + // Expect an arbitrary number of times. + var threeTimes = optional.times(3); + + // Expect a sequence of patterns. + var doraTheExplorer = chain([ + match('Dora').space(), + match('the').space(), + match('Explorer').space(), + ]); + + // Choose exactly one of a set of patterns, whichever + // appears first. + var alt = any([ + match('1'), + match('11'), + match('111'), + ]); + + // Choose the *longest* match for any of the given alternatives. + var alt2 = longest([ + match('1'), + match('11'), + match('111'), + ]); + + // Friendly operators + var fooOrNumber = foo | number; + var fooAndNumber = foo & number; + var notFoo = ~foo; +} +``` + +## Error Messages +Parsers without descriptive error messages can lead to frustrating dead-ends +for end-users. Fortunately, `combinator` is built with error handling in mind. + +```dart +void main(Parser parser) { + // Append an arbitrary error message to a parser if it is not matched. + var withError = parser.error(errorMessage: 'Hey!!! Wrong!!!'); + + // You can also set the severity of an error. + var asHint = parser.error(severity: SyntaxErrorSeverity.hint); + + // Constructs like `any`, `chain`, and `longest` support this as well. + var foo = longest([ + parser.error(errorMessage: 'foo'), + parser.error(errorMessage: 'bar') + ], errorMessage: 'Expected a "foo" or a "bar"'); + + // If multiple errors are present at one location, + // it can create a lot of noise. + // + // Use `foldErrors` to only take one error at a given location. + var lessNoise = parser.foldErrors(); +} +``` + +## Whitespaces +Handling optional whitespace is dead-easy: + +```dart +void main(Parser parser) { + var optionalSpace = parser.space(); +} +``` + +## For Programming Languages +`combinator` was conceived to make writing parsers for complex grammars easier, +namely programming languages. Thus, there are functions built-in to make common constructs +easier: + +```dart +void main(Parser parser) { + var array = parser + .separatedByComma() + .surroundedBySquareBrackets(defaultValue: []); + + var braces = parser.surroundedByCurlyBraces(); + + var sep = parser.separatedBy(match('!').space()); +} +``` + +## Differences between this and Petitparser +* `combinator` makes extensive use of Dart's dynamic typing +* `combinator` supports detailed error messages (with configurable severity) +* `combinator` keeps track of locations (ex. `line 1: 3`) \ No newline at end of file diff --git a/packages/combinator/analysis_options.yaml b/packages/combinator/analysis_options.yaml new file mode 100644 index 00000000..bbc55fe4 --- /dev/null +++ b/packages/combinator/analysis_options.yaml @@ -0,0 +1,4 @@ +analyzer: + strong-mode: + implicit-casts: false + #implicit-dynamic: false \ No newline at end of file diff --git a/packages/combinator/combinator.iml b/packages/combinator/combinator.iml new file mode 100644 index 00000000..75734c90 --- /dev/null +++ b/packages/combinator/combinator.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/combinator/example/basic_auth.dart b/packages/combinator/example/basic_auth.dart new file mode 100644 index 00000000..e6697590 --- /dev/null +++ b/packages/combinator/example/basic_auth.dart @@ -0,0 +1,55 @@ +// Run this with "Basic QWxhZGRpbjpPcGVuU2VzYW1l" + +import 'dart:convert'; +import 'dart:io'; +import 'package:combinator/combinator.dart'; +import 'package:string_scanner/string_scanner.dart'; + +/// Parse a part of a decoded Basic auth string. +/// +/// Namely, the `username` or `password` in `{username}:{password}`. +final Parser string = + match(new RegExp(r'[^:$]+'), errorMessage: 'Expected a string.') + .value((r) => r.span.text); + +/// Transforms `{username}:{password}` to `{"username": username, "password": password}`. +final Parser> credentials = chain([ + string.opt(), + match(':'), + string.opt(), +]).map>( + (r) => {'username': r.value[0], 'password': r.value[2]}); + +/// We can actually embed a parser within another parser. +/// +/// This is used here to BASE64URL-decode a string, and then +/// parse the decoded string. +final Parser credentialString = match>( + new RegExp(r'([^\n$]+)'), + errorMessage: 'Expected a credential string.') + .value((r) { + var decoded = utf8.decode(base64Url.decode(r.span.text)); + var scanner = new SpanScanner(decoded); + return credentials.parse(scanner).value; +}); + +final Parser basic = match('Basic').space(); + +final Parser basicAuth = basic.then(credentialString).index(1); + +void main() { + while (true) { + stdout.write('Enter a basic auth value: '); + var line = stdin.readLineSync(); + var scanner = new SpanScanner(line, sourceUrl: 'stdin'); + var result = basicAuth.parse(scanner); + + if (!result.successful) { + for (var error in result.errors) { + print(error.toolString); + print(error.span.highlight(color: true)); + } + } else + print(result.value); + } +} diff --git a/packages/combinator/example/calculator.dart b/packages/combinator/example/calculator.dart new file mode 100644 index 00000000..59a436ee --- /dev/null +++ b/packages/combinator/example/calculator.dart @@ -0,0 +1,70 @@ +import 'dart:math'; +import 'dart:io'; +import 'package:combinator/combinator.dart'; +import 'package:string_scanner/string_scanner.dart'; + +/// Note: This grammar does not handle precedence, for the sake of simplicity. +Parser calculatorGrammar() { + var expr = reference(); + + var number = match(new RegExp(r'-?[0-9]+(\.[0-9]+)?')) + .value((r) => num.parse(r.span.text)); + + var hex = match(new RegExp(r'0x([A-Fa-f0-9]+)')) + .map((r) => int.parse(r.scanner.lastMatch[1], radix: 16)); + + var binary = match(new RegExp(r'([0-1]+)b')) + .map((r) => int.parse(r.scanner.lastMatch[1], radix: 2)); + + var alternatives = >[]; + + void registerBinary(String op, num Function(num, num) f) { + alternatives.add( + chain([ + expr.space(), + match(op).space(), + expr.space(), + ]).map((r) => f(r.value[0], r.value[2])), + ); + } + + registerBinary('**', (a, b) => pow(a, b)); + registerBinary('*', (a, b) => a * b); + registerBinary('/', (a, b) => a / b); + registerBinary('%', (a, b) => a % b); + registerBinary('+', (a, b) => a + b); + registerBinary('-', (a, b) => a - b); + registerBinary('^', (a, b) => a.toInt() ^ b.toInt()); + registerBinary('&', (a, b) => a.toInt() & b.toInt()); + registerBinary('|', (a, b) => a.toInt() | b.toInt()); + + alternatives.addAll([ + number, + hex, + binary, + expr.parenthesized(), + ]); + + expr.parser = longest(alternatives); + + return expr; +} + +void main() { + var calculator = calculatorGrammar(); + + while (true) { + stdout.write('Enter an expression: '); + var line = stdin.readLineSync(); + var scanner = new SpanScanner(line, sourceUrl: 'stdin'); + var result = calculator.parse(scanner); + + if (!result.successful) { + for (var error in result.errors) { + stderr.writeln(error.toolString); + stderr.writeln(error.span.highlight(color: true)); + } + } else + print(result.value); + } +} diff --git a/packages/combinator/example/delimiter.dart b/packages/combinator/example/delimiter.dart new file mode 100644 index 00000000..0028accc --- /dev/null +++ b/packages/combinator/example/delimiter.dart @@ -0,0 +1,28 @@ +import 'dart:io'; +import 'package:combinator/combinator.dart'; +import 'package:string_scanner/string_scanner.dart'; + +final Parser id = + match(RegExp(r'[A-Za-z]+')).value((r) => r.span.text); + +// We can use `separatedBy` to easily construct parser +// that can be matched multiple times, separated by another +// pattern. +// +// This is useful for parsing arrays or map literals. +main() { + while (true) { + stdout.write('Enter a string (ex "a,b,c"): '); + var line = stdin.readLineSync(); + var scanner = new SpanScanner(line, sourceUrl: 'stdin'); + var result = id.separatedBy(match(',').space()).parse(scanner); + + if (!result.successful) { + for (var error in result.errors) { + print(error.toolString); + print(error.span.highlight(color: true)); + } + } else + print(result.value); + } +} diff --git a/packages/combinator/example/json.dart b/packages/combinator/example/json.dart new file mode 100644 index 00000000..10dd0612 --- /dev/null +++ b/packages/combinator/example/json.dart @@ -0,0 +1,70 @@ +import 'dart:io'; +import 'package:combinator/combinator.dart'; +import 'package:string_scanner/string_scanner.dart'; + +Parser jsonGrammar() { + var expr = reference(); + + // Parse a number + var number = match(new RegExp(r'-?[0-9]+(\.[0-9]+)?'), + errorMessage: 'Expected a number.') + .value( + (r) => num.parse(r.span.text), + ); + + // Parse a string (no escapes supported, because lazy). + var string = + match(new RegExp(r'"[^"]*"'), errorMessage: 'Expected a string.').value( + (r) => r.span.text.substring(1, r.span.text.length - 1), + ); + + // Parse an array + var array = expr + .space() + .separatedByComma() + .surroundedBySquareBrackets(defaultValue: []); + + // KV pair + var keyValuePair = chain([ + string.space(), + match(':').space(), + expr.error(errorMessage: 'Missing expression.'), + ]).castDynamic().cast().value((r) => {r.value[0]: r.value[2]}); + + // Parse an object. + var object = keyValuePair + .separatedByComma() + .castDynamic() + .surroundedByCurlyBraces(defaultValue: {}); + + expr.parser = longest( + [ + array, + number, + string, + object.error(), + ], + errorMessage: 'Expected an expression.', + ).space(); + + return expr.foldErrors(); +} + +main() { + var JSON = jsonGrammar(); + + while (true) { + stdout.write('Enter some JSON: '); + var line = stdin.readLineSync(); + var scanner = new SpanScanner(line, sourceUrl: 'stdin'); + var result = JSON.parse(scanner); + + if (!result.successful) { + for (var error in result.errors) { + print(error.toolString); + print(error.span.highlight(color: true)); + } + } else + print(result.value); + } +} diff --git a/packages/combinator/example/main.dart b/packages/combinator/example/main.dart new file mode 100644 index 00000000..96dfc233 --- /dev/null +++ b/packages/combinator/example/main.dart @@ -0,0 +1,37 @@ +import 'dart:io'; +import 'package:combinator/combinator.dart'; +import 'package:string_scanner/string_scanner.dart'; + +final Parser minus = match('-'); + +final Parser digit = + match(new RegExp(r'[0-9]'), errorMessage: 'Expected a number'); + +final Parser digits = digit.plus(); + +final Parser dot = match('.'); + +final Parser decimal = ( // digits, (dot, digits)? + digits & (dot & digits).opt() // + ); + +final Parser number = // + (minus.opt() & decimal) // minus?, decimal + .map((r) => num.parse(r.span.text)); + +main() { + while (true) { + stdout.write('Enter a number: '); + var line = stdin.readLineSync(); + var scanner = new SpanScanner(line, sourceUrl: 'stdin'); + var result = number.parse(scanner); + + if (!result.successful) { + for (var error in result.errors) { + stderr.writeln(error.toolString); + stderr.writeln(error.span.highlight(color: true)); + } + } else + print(result.value); + } +} diff --git a/packages/combinator/example/query_string.dart b/packages/combinator/example/query_string.dart new file mode 100644 index 00000000..a1f8a3f2 --- /dev/null +++ b/packages/combinator/example/query_string.dart @@ -0,0 +1,44 @@ +// For some reason, this cannot be run in checked mode??? + +import 'dart:io'; +import 'package:combinator/combinator.dart'; +import 'package:string_scanner/string_scanner.dart'; + +final Parser key = + match(RegExp(r'[^=&\n]+'), errorMessage: 'Missing k/v') + .value((r) => r.span.text); + +final Parser value = key.map((r) => Uri.decodeQueryComponent(r.value)); + +final Parser pair = chain([ + key, + match('='), + value, +]).map((r) { + return { + r.value[0]: r.value[2], + }; +}); + +final Parser pairs = pair + .separatedBy(match(r'&')) + .map((r) => r.value.reduce((a, b) => a..addAll(b))); + +final Parser queryString = pairs.opt(); + +main() { + while (true) { + stdout.write('Enter a query string: '); + var line = stdin.readLineSync(); + var scanner = new SpanScanner(line, sourceUrl: 'stdin'); + var result = pairs.parse(scanner); + + if (!result.successful) { + for (var error in result.errors) { + print(error.toolString); + print(error.span.highlight(color: true)); + } + } else + print(result.value); + } +} diff --git a/packages/combinator/example/sexp.dart b/packages/combinator/example/sexp.dart new file mode 100644 index 00000000..5e7d4083 --- /dev/null +++ b/packages/combinator/example/sexp.dart @@ -0,0 +1,84 @@ +import 'dart:collection'; +import 'dart:io'; +import 'dart:math'; +import 'package:combinator/combinator.dart'; +import 'package:string_scanner/string_scanner.dart'; +import 'package:tuple/tuple.dart'; + +void main() { + var expr = reference(); + var symbols = {}; + + void registerFunction(String name, int nArgs, Function(List) f) { + symbols[name] = new Tuple2(nArgs, f); + } + + registerFunction('**', 2, (args) => pow(args[0], args[1])); + registerFunction('*', 2, (args) => args[0] * args[1]); + registerFunction('/', 2, (args) => args[0] / args[1]); + registerFunction('%', 2, (args) => args[0] % args[1]); + registerFunction('+', 2, (args) => args[0] + args[1]); + registerFunction('-', 2, (args) => args[0] - args[1]); + registerFunction('.', 1, (args) => args[0].toDouble()); + registerFunction('print', 1, (args) { + print(args[0]); + return args[0]; + }); + + var number = match(new RegExp(r'[0-9]+(\.[0-9]+)?'), + errorMessage: 'Expected a number.') + .map((r) => num.parse(r.span.text)); + + var id = match( + new RegExp( + r'[A-Za-z_!\\$",\\+-\\./:;\\?<>%&\\*@\[\]\\{\}\\|`\\^~][A-Za-z0-9_!\\$",\\+-\\./:;\\?<>%&\*@\[\]\\{\}\\|`\\^~]*'), + errorMessage: 'Expected an ID') + .map((r) => + symbols[r.span.text] ??= throw "Undefined symbol: '${r.span.text}'"); + + var atom = number.castDynamic().or(id); + + var list = expr.space().times(2, exact: false).map((r) { + try { + var out = []; + var q = new Queue.from(r.value.reversed); + + while (q.isNotEmpty) { + var current = q.removeFirst(); + if (current is! Tuple2) + out.insert(0, current); + else { + var args = []; + for (int i = 0; i < (current.item1 as num); i++) + args.add(out.removeLast()); + out.add(current.item2(args)); + } + } + + return out.length == 1 ? out.first : out; + } catch (_) { + return []; + } + }); + + expr.parser = longest([ + list, + atom, + expr.parenthesized(), + ]); //list | atom | expr.parenthesized(); + + while (true) { + stdout.write('> '); + var line = stdin.readLineSync(); + var result = expr.parse(new SpanScanner(line)); + + if (result.errors.isNotEmpty) { + for (var error in result.errors) { + print(error.toolString); + print(error.message); + } + } else { + print(result.value); + } + } +} diff --git a/packages/combinator/example/tuple.dart b/packages/combinator/example/tuple.dart new file mode 100644 index 00000000..453c7e61 --- /dev/null +++ b/packages/combinator/example/tuple.dart @@ -0,0 +1,14 @@ +import 'package:combinator/combinator.dart'; +import 'package:string_scanner/string_scanner.dart'; + +void main() { + var pub = match('pub').map((r) => r.span.text).space(); + var dart = match('dart').map((r) => 24).space(); + var lang = match('lang').map((r) => true).space(); + + // Parses a Tuple3 + var grammar = tuple3(pub, dart, lang); + + var scanner = SpanScanner('pub dart lang'); + print(grammar.parse(scanner).value); +} diff --git a/packages/combinator/lib/combinator.dart b/packages/combinator/lib/combinator.dart new file mode 100644 index 00000000..79e4c074 --- /dev/null +++ b/packages/combinator/lib/combinator.dart @@ -0,0 +1,2 @@ +export 'src/combinator/combinator.dart'; +export 'src/error.dart'; diff --git a/packages/combinator/lib/src/combinator/advance.dart b/packages/combinator/lib/src/combinator/advance.dart new file mode 100644 index 00000000..01506dc2 --- /dev/null +++ b/packages/combinator/lib/src/combinator/advance.dart @@ -0,0 +1,26 @@ +part of lex.src.combinator; + +class _Advance extends Parser { + final Parser parser; + final int amount; + + _Advance(this.parser, this.amount); + + @override + ParseResult __parse(ParseArgs args) { + var result = parser._parse(args.increaseDepth()).change(parser: this); + if (result.successful) args.scanner.position += amount; + return result; + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('advance($amount) (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/any.dart b/packages/combinator/lib/src/combinator/any.dart new file mode 100644 index 00000000..618d2b58 --- /dev/null +++ b/packages/combinator/lib/src/combinator/any.dart @@ -0,0 +1,85 @@ +part of lex.src.combinator; + +/// Matches any one of the given [parsers]. +/// +/// If [backtrack] is `true` (default), a failed parse will not modify the scanner state. +/// +/// You can provide a custom [errorMessage]. You can set it to `false` to not +/// generate any error at all. +Parser any(Iterable> parsers, + {bool backtrack: true, errorMessage, SyntaxErrorSeverity severity}) { + return new _Any(parsers, backtrack != false, errorMessage, + severity ?? SyntaxErrorSeverity.error); +} + +class _Any extends Parser { + final Iterable> parsers; + final bool backtrack; + final errorMessage; + final SyntaxErrorSeverity severity; + + _Any(this.parsers, this.backtrack, this.errorMessage, this.severity); + + @override + ParseResult _parse(ParseArgs args) { + var inactive = parsers + .where((p) => !args.trampoline.isActive(p, args.scanner.position)); + + if (inactive.isEmpty) { + return new ParseResult(args.trampoline, args.scanner, this, false, []); + } + + var errors = []; + int replay = args.scanner.position; + + for (var parser in inactive) { + var result = parser._parse(args.increaseDepth()); + + if (result.successful) + return result; + else { + if (backtrack) args.scanner.position = replay; + if (parser is _Alt) errors.addAll(result.errors); + } + } + + if (errorMessage != false) { + errors.add( + new SyntaxError( + severity, + errorMessage?.toString() ?? + 'No match found for ${parsers.length} alternative(s)', + args.scanner.emptySpan, + ), + ); + } + + return new ParseResult(args.trampoline, args.scanner, this, false, errors); + } + + @override + ParseResult __parse(ParseArgs args) { + // Never called + return null; + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('any(${parsers.length}) (') + ..indent(); + int i = 1; + + for (var parser in parsers) { + buffer + ..writeln('#${i++}:') + ..indent(); + parser.stringify(buffer); + buffer.outdent(); + } + + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/cache.dart b/packages/combinator/lib/src/combinator/cache.dart new file mode 100644 index 00000000..b07b38e0 --- /dev/null +++ b/packages/combinator/lib/src/combinator/cache.dart @@ -0,0 +1,26 @@ +part of lex.src.combinator; + +class _Cache extends Parser { + final Map> _cache = {}; + final Parser parser; + + _Cache(this.parser); + + @override + ParseResult __parse(ParseArgs args) { + return _cache.putIfAbsent(args.scanner.position, () { + return parser._parse(args.increaseDepth()); + }).change(parser: this); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('cache(${_cache.length}) (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/cast.dart b/packages/combinator/lib/src/combinator/cast.dart new file mode 100644 index 00000000..b1e91d0e --- /dev/null +++ b/packages/combinator/lib/src/combinator/cast.dart @@ -0,0 +1,63 @@ +part of lex.src.combinator; + +class _Cast extends Parser { + final Parser parser; + + _Cast(this.parser); + + @override + ParseResult __parse(ParseArgs args) { + var result = parser._parse(args.increaseDepth()); + return new ParseResult( + args.trampoline, + args.scanner, + this, + result.successful, + result.errors, + span: result.span, + value: result.value as U, + ); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('cast<$U> (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} + +class _CastDynamic extends Parser { + final Parser parser; + + _CastDynamic(this.parser); + + @override + ParseResult __parse(ParseArgs args) { + var result = parser._parse(args.increaseDepth()); + return new ParseResult( + args.trampoline, + args.scanner, + this, + result.successful, + result.errors, + span: result.span, + value: result.value, + ); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('cast (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/chain.dart b/packages/combinator/lib/src/combinator/chain.dart new file mode 100644 index 00000000..3925aed4 --- /dev/null +++ b/packages/combinator/lib/src/combinator/chain.dart @@ -0,0 +1,105 @@ +part of lex.src.combinator; + +/// Expects to parse a sequence of [parsers]. +/// +/// If [failFast] is `true` (default), then the first failure to parse will abort the parse. +ListParser chain(Iterable> parsers, + {bool failFast: true, SyntaxErrorSeverity severity}) { + return new _Chain( + parsers, failFast != false, severity ?? SyntaxErrorSeverity.error); +} + +class _Alt extends Parser { + final Parser parser; + final String errorMessage; + final SyntaxErrorSeverity severity; + + _Alt(this.parser, this.errorMessage, this.severity); + + @override + ParseResult __parse(ParseArgs args) { + var result = parser._parse(args.increaseDepth()); + return result.successful + ? result + : result.addErrors([ + new SyntaxError(severity ?? SyntaxErrorSeverity.error, errorMessage, + result.span ?? args.scanner.emptySpan), + ]); + } + + @override + void stringify(CodeBuffer buffer) { + parser.stringify(buffer); + } +} + +class _Chain extends ListParser { + final Iterable> parsers; + final bool failFast; + final SyntaxErrorSeverity severity; + + _Chain(this.parsers, this.failFast, this.severity); + + @override + ParseResult> __parse(ParseArgs args) { + var errors = []; + var results = []; + var spans = []; + bool successful = true; + + for (var parser in parsers) { + var result = parser._parse(args.increaseDepth()); + + if (!result.successful) { + if (parser is _Alt) errors.addAll(result.errors); + + if (failFast) { + return new ParseResult( + args.trampoline, args.scanner, this, false, result.errors); + } + + successful = false; + } + + results.add(result.value); + + if (result.span != null) spans.add(result.span); + } + + FileSpan span; + + if (spans.isNotEmpty) { + span = spans.reduce((a, b) => a.expand(b)); + } + + return new ParseResult>( + args.trampoline, + args.scanner, + this, + successful, + errors, + span: span, + value: new List.unmodifiable(results), + ); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('chain(${parsers.length}) (') + ..indent(); + int i = 1; + + for (var parser in parsers) { + buffer + ..writeln('#${i++}:') + ..indent(); + parser.stringify(buffer); + buffer.outdent(); + } + + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/check.dart b/packages/combinator/lib/src/combinator/check.dart new file mode 100644 index 00000000..1f2159f0 --- /dev/null +++ b/packages/combinator/lib/src/combinator/check.dart @@ -0,0 +1,42 @@ +part of lex.src.combinator; + +class _Check extends Parser { + final Parser parser; + final Matcher matcher; + final String errorMessage; + final SyntaxErrorSeverity severity; + + _Check(this.parser, this.matcher, this.errorMessage, this.severity); + + @override + ParseResult __parse(ParseArgs args) { + var matchState = {}; + var result = parser._parse(args.increaseDepth()).change(parser: this); + if (!result.successful) + return result; + else if (!matcher.matches(result.value, matchState)) { + return result.change(successful: false).addErrors([ + new SyntaxError( + severity, + errorMessage ?? + matcher.describe(new StringDescription('Expected ')).toString() + + '.', + result.span, + ), + ]); + } else + return result; + } + + @override + void stringify(CodeBuffer buffer) { + var d = matcher.describe(new StringDescription()); + buffer + ..writeln('check($d) (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/combinator.dart b/packages/combinator/lib/src/combinator/combinator.dart new file mode 100644 index 00000000..2b083a25 --- /dev/null +++ b/packages/combinator/lib/src/combinator/combinator.dart @@ -0,0 +1,381 @@ +library lex.src.combinator; + +import 'dart:collection'; + +import 'package:code_buffer/code_buffer.dart'; +import 'package:matcher/matcher.dart'; +import 'package:source_span/source_span.dart'; +import 'package:string_scanner/string_scanner.dart'; +import 'package:tuple/tuple.dart'; +import '../error.dart'; + +part 'any.dart'; + +part 'advance.dart'; + +part 'cache.dart'; + +part 'cast.dart'; + +part 'chain.dart'; + +part 'check.dart'; + +part 'compare.dart'; + +part 'fold_errors.dart'; + +part 'index.dart'; + +part 'longest.dart'; + +part 'map.dart'; + +part 'match.dart'; + +part 'max_depth.dart'; + +part 'negate.dart'; + +part 'opt.dart'; + +part 'recursion.dart'; + +part 'reduce.dart'; + +part 'reference.dart'; + +part 'repeat.dart'; + +part 'safe.dart'; + +part 'to_list.dart'; + +part 'util.dart'; + +part 'value.dart'; + +class ParseArgs { + final Trampoline trampoline; + final SpanScanner scanner; + final int depth; + + ParseArgs(this.trampoline, this.scanner, this.depth); + + ParseArgs increaseDepth() => new ParseArgs(trampoline, scanner, depth + 1); +} + +/// A parser combinator, which can parse very complicated grammars in a manageable manner. +abstract class Parser { + ParseResult __parse(ParseArgs args); + + ParseResult _parse(ParseArgs args) { + var pos = args.scanner.position; + + if (args.trampoline.hasMemoized(this, pos)) + return args.trampoline.getMemoized(this, pos); + + if (args.trampoline.isActive(this, pos)) + return new ParseResult(args.trampoline, args.scanner, this, false, []); + + args.trampoline.enter(this, pos); + var result = __parse(args); + args.trampoline.memoize(this, pos, result); + args.trampoline.exit(this); + return result; + } + + /// Parses text from a [SpanScanner]. + ParseResult parse(SpanScanner scanner, [int depth = 1]) { + var args = new ParseArgs(new Trampoline(), scanner, depth); + return _parse(args); + } + + /// Skips forward a certain amount of steps after parsing, if it was successful. + Parser forward(int amount) => new _Advance(this, amount); + + /// Moves backward a certain amount of steps after parsing, if it was successful. + Parser back(int amount) => new _Advance(this, amount * -1); + + /// Casts this parser to produce [U] objects. + Parser cast() => new _Cast(this); + + /// Casts this parser to produce [dynamic] objects. + Parser castDynamic() => new _CastDynamic(this); + + /// Runs the given function, which changes the returned [ParseResult] into one relating to a [U] object. + Parser change(ParseResult Function(ParseResult) f) { + return new _Change(this, f); + } + + /// Validates the parse result against a [Matcher]. + /// + /// You can provide a custom [errorMessage]. + Parser check(Matcher matcher, + {String errorMessage, SyntaxErrorSeverity severity}) => + new _Check( + this, matcher, errorMessage, severity ?? SyntaxErrorSeverity.error); + + /// Binds an [errorMessage] to a copy of this parser. + Parser error({String errorMessage, SyntaxErrorSeverity severity}) => + new _Alt(this, errorMessage, severity ?? SyntaxErrorSeverity.error); + + /// Removes multiple errors that occur in the same spot; this can reduce noise in parser output. + Parser foldErrors({bool equal(SyntaxError a, SyntaxError b)}) { + equal ??= (b, e) => b.span.start.offset == e.span.start.offset; + return new _FoldErrors(this, equal); + } + + /// Transforms the parse result using a unary function. + Parser map(U Function(ParseResult) f) { + return new _Map(this, f); + } + + /// Prevents recursion past a certain [depth], preventing stack overflow errors. + Parser maxDepth(int depth) => new _MaxDepth(this, depth); + + Parser operator ~() => negate(); + + /// Ensures this pattern is not matched. + /// + /// You can provide an [errorMessage]. + Parser negate({String errorMessage, SyntaxErrorSeverity severity}) => + new _Negate(this, errorMessage, severity ?? SyntaxErrorSeverity.error); + + /// Caches the results of parse attempts at various locations within the source text. + /// + /// Use this to prevent excessive recursion. + Parser cache() => new _Cache(this); + + Parser operator &(Parser other) => and(other); + + /// Consumes `this` and another parser, but only considers the result of `this` parser. + Parser and(Parser other) => then(other).change((r) { + return new ParseResult( + r.trampoline, + r.scanner, + this, + r.successful, + r.errors, + span: r.span, + value: (r.value != null ? r.value[0] : r.value) as T, + ); + }); + + Parser operator |(Parser other) => or(other); + + /// Shortcut for [or]-ing two parsers. + Parser or(Parser other) => any([this, other]); + + /// Parses this sequence one or more times. + ListParser plus() => times(1, exact: false); + + /// Safely escapes this parser when an error occurs. + /// + /// The generated parser only runs once; repeated uses always exit eagerly. + Parser safe( + {bool backtrack: true, + String errorMessage, + SyntaxErrorSeverity severity}) => + new _Safe( + this, backtrack, errorMessage, severity ?? SyntaxErrorSeverity.error); + + Parser> separatedByComma() => + separatedBy(match>(',').space()); + + /// Expects to see an infinite amounts of the pattern, separated by the [other] pattern. + /// + /// Use this as a shortcut to parse arrays, parameter lists, etc. + Parser> separatedBy(Parser other) { + var suffix = other.then(this).index(1).cast(); + return this.then(suffix.star()).map((r) { + var preceding = + r.value.isEmpty ? [] : (r.value[0] == null ? [] : [r.value[0]]); + var out = new List.from(preceding); + if (r.value[1] != null) out.addAll(r.value[1] as Iterable); + return out; + }); + } + + Parser surroundedByCurlyBraces({T defaultValue}) => opt() + .surroundedBy(match('{').space(), match('}').space()) + .map((r) => r.value ?? defaultValue); + + Parser surroundedBySquareBrackets({T defaultValue}) => opt() + .surroundedBy(match('[').space(), match(']').space()) + .map((r) => r.value ?? defaultValue); + + /// Expects to see the pattern, surrounded by the others. + /// + /// If no [right] is provided, it expects to see the same pattern on both sides. + /// Use this parse things like parenthesized expressions, arrays, etc. + Parser surroundedBy(Parser left, [Parser right]) { + return chain([ + left, + this, + right ?? left, + ]).index(1).castDynamic().cast(); + } + + /// Parses `this`, either as-is or wrapped in parentheses. + Parser maybeParenthesized() { + return any([parenthesized(), this]); + } + + /// Parses `this`, wrapped in parentheses. + Parser parenthesized() => + surroundedBy(match('(').space(), match(')').space()); + + /// Consumes any trailing whitespace. + Parser space() => trail(new RegExp(r'[ \n\r\t]+')); + + /// Consumes 0 or more instance(s) of this parser. + ListParser star({bool backtrack: true}) => + times(1, exact: false, backtrack: backtrack).opt(); + + /// Shortcut for [chain]-ing two parsers together. + ListParser then(Parser other) => chain([this, other]); + + /// Casts this instance into a [ListParser]. + ListParser toList() => new _ToList(this); + + /// Consumes and ignores any trailing occurrences of [pattern]. + Parser trail(Pattern pattern) => + then(match(pattern).opt()).first().cast(); + + /// Expect this pattern a certain number of times. + /// + /// If [exact] is `false` (default: `true`), then the generated parser will accept + /// an infinite amount of occurrences after the specified [count]. + /// + /// You can provide custom error messages for when there are [tooFew] or [tooMany] occurrences. + ListParser times(int count, + {bool exact: true, + String tooFew, + String tooMany, + bool backtrack: true, + SyntaxErrorSeverity severity}) { + return new _Repeat(this, count, exact, tooFew, tooMany, backtrack, + severity ?? SyntaxErrorSeverity.error); + } + + /// Produces an optional copy of this parser. + /// + /// If [backtrack] is `true` (default), then a failed parse will not + /// modify the scanner state. + Parser opt({bool backtrack: true}) => new _Opt(this, backtrack); + + /// Sets the value of the [ParseResult]. + Parser value(T Function(ParseResult) f) { + return new _Value(this, f); + } + + /// Prints a representation of this parser, ideally without causing a stack overflow. + void stringify(CodeBuffer buffer); +} + +/// A [Parser] that produces [List]s of a type [T]. +abstract class ListParser extends Parser> { + /// Shortcut for calling [index] with `0`. + Parser first() => index(0); + + /// Modifies this parser to only return the value at the given index [i]. + Parser index(int i) => new _Index(this, i); + + /// Shortcut for calling [index] with the greatest-possible index. + Parser last() => index(-1); + + /// Modifies this parser to call `List.reduce` on the parsed values. + Parser reduce(T Function(T, T) combine) => new _Reduce(this, combine); + + /// Sorts the parsed values, using the given [Comparator]. + ListParser sort(Comparator compare) => new _Compare(this, compare); + + @override + ListParser opt({bool backtrack: true}) => new _ListOpt(this, backtrack); + + /// Modifies this parser, returning only the values that match a predicate. + Parser> where(bool Function(T) f) => + map>((r) => r.value.where(f).toList()); + + /// Condenses a [ListParser] into having a value of the combined span's text. + Parser flatten() => map((r) => r.span.text); +} + +/// Prevents stack overflow in recursive parsers. +class Trampoline { + final Map> _active = {}; + final Map>> _memo = {}; + + bool hasMemoized(Parser parser, int position) { + var list = _memo[parser]; + return list?.any((t) => t.item1 == position) == true; + } + + ParseResult getMemoized(Parser parser, int position) { + return _memo[parser].firstWhere((t) => t.item1 == position).item2 + as ParseResult; + } + + void memoize(Parser parser, int position, ParseResult result) { + if (result != null) { + var list = _memo.putIfAbsent(parser, () => []); + var tuple = new Tuple2(position, result); + if (!list.contains(tuple)) list.add(tuple); + } + } + + bool isActive(Parser parser, int position) { + if (!_active.containsKey(parser)) return false; + var q = _active[parser]; + if (q.isEmpty) return false; + //return q.contains(position); + return q.first == position; + } + + void enter(Parser parser, int position) { + _active.putIfAbsent(parser, () => new Queue()).addFirst(position); + } + + void exit(Parser parser) { + if (_active.containsKey(parser)) _active[parser].removeFirst(); + } +} + +/// The result generated by a [Parser]. +class ParseResult { + final Parser parser; + final bool successful; + final Iterable errors; + final FileSpan span; + final T value; + final SpanScanner scanner; + final Trampoline trampoline; + + ParseResult( + this.trampoline, this.scanner, this.parser, this.successful, this.errors, + {this.span, this.value}); + + ParseResult change( + {Parser parser, + bool successful, + Iterable errors, + FileSpan span, + T value}) { + return new ParseResult( + trampoline, + scanner, + parser ?? this.parser, + successful ?? this.successful, + errors ?? this.errors, + span: span ?? this.span, + value: value ?? this.value, + ); + } + + ParseResult addErrors(Iterable errors) { + return change( + errors: new List.from(this.errors)..addAll(errors), + ); + } +} diff --git a/packages/combinator/lib/src/combinator/compare.dart b/packages/combinator/lib/src/combinator/compare.dart new file mode 100644 index 00000000..99447ac1 --- /dev/null +++ b/packages/combinator/lib/src/combinator/compare.dart @@ -0,0 +1,38 @@ +part of lex.src.combinator; + +class _Compare extends ListParser { + final ListParser parser; + final Comparator compare; + + _Compare(this.parser, this.compare); + + @override + ParseResult> __parse(ParseArgs args) { + var result = parser._parse(args.increaseDepth()); + if (!result.successful) return result; + + result = result.change( + value: result.value?.isNotEmpty == true ? result.value : []); + result = result.change(value: new List.from(result.value)); + return new ParseResult>( + args.trampoline, + args.scanner, + this, + true, + [], + span: result.span, + value: result.value..sort(compare), + ); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('sort($compare) (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/fold_errors.dart b/packages/combinator/lib/src/combinator/fold_errors.dart new file mode 100644 index 00000000..6d15c69a --- /dev/null +++ b/packages/combinator/lib/src/combinator/fold_errors.dart @@ -0,0 +1,29 @@ +part of lex.src.combinator; + +class _FoldErrors extends Parser { + final Parser parser; + final bool Function(SyntaxError, SyntaxError) equal; + + _FoldErrors(this.parser, this.equal); + + @override + ParseResult __parse(ParseArgs args) { + var result = parser._parse(args.increaseDepth()).change(parser: this); + var errors = result.errors.fold>([], (out, e) { + if (!out.any((b) => equal(e, b))) out.add(e); + return out; + }); + return result.change(errors: errors); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('fold errors (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/index.dart b/packages/combinator/lib/src/combinator/index.dart new file mode 100644 index 00000000..0a57c3d7 --- /dev/null +++ b/packages/combinator/lib/src/combinator/index.dart @@ -0,0 +1,38 @@ +part of lex.src.combinator; + +class _Index extends Parser { + final ListParser parser; + final int index; + + _Index(this.parser, this.index); + + @override + ParseResult __parse(ParseArgs args) { + var result = parser._parse(args.increaseDepth()); + Object value; + + if (result.successful) + value = index == -1 ? result.value.last : result.value.elementAt(index); + + return new ParseResult( + args.trampoline, + args.scanner, + this, + result.successful, + result.errors, + span: result.span, + value: value as T, + ); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('index($index) (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/longest.dart b/packages/combinator/lib/src/combinator/longest.dart new file mode 100644 index 00000000..dec48df9 --- /dev/null +++ b/packages/combinator/lib/src/combinator/longest.dart @@ -0,0 +1,115 @@ +part of lex.src.combinator; + +/// Matches any one of the given [parsers]. +/// +/// You can provide a custom [errorMessage]. +Parser longest(Iterable> parsers, + {Object errorMessage, SyntaxErrorSeverity severity}) { + return new _Longest( + parsers, errorMessage, severity ?? SyntaxErrorSeverity.error); +} + +class _Longest extends Parser { + final Iterable> parsers; + final Object errorMessage; + final SyntaxErrorSeverity severity; + + _Longest(this.parsers, this.errorMessage, this.severity); + + @override + ParseResult _parse(ParseArgs args) { + var inactive = parsers + .toList() + .where((p) => !args.trampoline.isActive(p, args.scanner.position)); + + if (inactive.isEmpty) { + return new ParseResult(args.trampoline, args.scanner, this, false, []); + } + + int replay = args.scanner.position; + var errors = []; + var results = >[]; + + for (var parser in inactive) { + var result = parser._parse(args.increaseDepth()); + + if (result.successful && result.span != null) + results.add(result); + else if (parser is _Alt) errors.addAll(result.errors); + + args.scanner.position = replay; + } + + if (results.isNotEmpty) { + results.sort((a, b) => b.span.length.compareTo(a.span.length)); + args.scanner.scan(results.first.span.text); + return results.first; + } + + if (errorMessage != false) + errors.add( + new SyntaxError( + severity, + errorMessage?.toString() ?? + 'No match found for ${parsers.length} alternative(s)', + args.scanner.emptySpan, + ), + ); + + return new ParseResult(args.trampoline, args.scanner, this, false, errors); + } + + @override + ParseResult __parse(ParseArgs args) { + int replay = args.scanner.position; + var errors = []; + var results = >[]; + + for (var parser in parsers) { + var result = parser._parse(args.increaseDepth()); + + if (result.successful) + results.add(result); + else if (parser is _Alt) errors.addAll(result.errors); + + args.scanner.position = replay; + } + + if (results.isNotEmpty) { + results.sort((a, b) => b.span.length.compareTo(a.span.length)); + args.scanner.scan(results.first.span.text); + return results.first; + } + + errors.add( + new SyntaxError( + severity, + errorMessage?.toString() ?? + 'No match found for ${parsers.length} alternative(s)', + args.scanner.emptySpan, + ), + ); + + return new ParseResult(args.trampoline, args.scanner, this, false, errors); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('longest(${parsers.length}) (') + ..indent(); + int i = 1; + + for (var parser in parsers) { + buffer + ..writeln('#${i++}:') + ..indent(); + parser.stringify(buffer); + buffer.outdent(); + } + + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/map.dart b/packages/combinator/lib/src/combinator/map.dart new file mode 100644 index 00000000..b1e210a3 --- /dev/null +++ b/packages/combinator/lib/src/combinator/map.dart @@ -0,0 +1,56 @@ +part of lex.src.combinator; + +class _Map extends Parser { + final Parser parser; + final U Function(ParseResult) f; + + _Map(this.parser, this.f); + + @override + ParseResult __parse(ParseArgs args) { + var result = parser._parse(args.increaseDepth()); + return new ParseResult( + args.trampoline, + args.scanner, + this, + result.successful, + result.errors, + span: result.span, + value: result.successful ? f(result) : null, + ); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('map<$U> (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} + +class _Change extends Parser { + final Parser parser; + final ParseResult Function(ParseResult) f; + + _Change(this.parser, this.f); + + @override + ParseResult __parse(ParseArgs args) { + return f(parser._parse(args.increaseDepth())).change(parser: this); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('change($f) (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/match.dart b/packages/combinator/lib/src/combinator/match.dart new file mode 100644 index 00000000..4ebfc1c6 --- /dev/null +++ b/packages/combinator/lib/src/combinator/match.dart @@ -0,0 +1,40 @@ +part of lex.src.combinator; + +/// Expects to match a given [pattern]. If it is not matched, you can provide a custom [errorMessage]. +Parser match(Pattern pattern, + {String errorMessage, SyntaxErrorSeverity severity}) => + new _Match(pattern, errorMessage, severity ?? SyntaxErrorSeverity.error); + +class _Match extends Parser { + final Pattern pattern; + final String errorMessage; + final SyntaxErrorSeverity severity; + + _Match(this.pattern, this.errorMessage, this.severity); + + @override + ParseResult __parse(ParseArgs args) { + var scanner = args.scanner; + if (!scanner.scan(pattern)) + return new ParseResult(args.trampoline, scanner, this, false, [ + new SyntaxError( + severity, + errorMessage ?? 'Expected "$pattern".', + scanner.emptySpan, + ), + ]); + return new ParseResult( + args.trampoline, + scanner, + this, + true, + [], + span: scanner.lastSpan, + ); + } + + @override + void stringify(CodeBuffer buffer) { + buffer.writeln('match($pattern)'); + } +} diff --git a/packages/combinator/lib/src/combinator/max_depth.dart b/packages/combinator/lib/src/combinator/max_depth.dart new file mode 100644 index 00000000..1c28a9d2 --- /dev/null +++ b/packages/combinator/lib/src/combinator/max_depth.dart @@ -0,0 +1,28 @@ +part of lex.src.combinator; + +class _MaxDepth extends Parser { + final Parser parser; + final int cap; + + _MaxDepth(this.parser, this.cap); + + @override + ParseResult __parse(ParseArgs args) { + if (args.depth > cap) { + return new ParseResult(args.trampoline, args.scanner, this, false, []); + } + + return parser._parse(args.increaseDepth()); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('max depth($cap) (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/negate.dart b/packages/combinator/lib/src/combinator/negate.dart new file mode 100644 index 00000000..db255afc --- /dev/null +++ b/packages/combinator/lib/src/combinator/negate.dart @@ -0,0 +1,51 @@ +part of lex.src.combinator; + +class _Negate extends Parser { + final Parser parser; + final String errorMessage; + final SyntaxErrorSeverity severity; + + _Negate(this.parser, this.errorMessage, this.severity); + + @override + ParseResult __parse(ParseArgs args) { + var result = parser._parse(args.increaseDepth()).change(parser: this); + + if (!result.successful) { + return new ParseResult( + args.trampoline, + args.scanner, + this, + true, + [], + span: result.span ?? args.scanner.lastSpan ?? args.scanner.emptySpan, + value: result.value, + ); + } + + result = result.change(successful: false); + + if (errorMessage != null) { + result = result.addErrors([ + new SyntaxError( + severity, + errorMessage, + result.span, + ), + ]); + } + + return result; + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('negate (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/opt.dart b/packages/combinator/lib/src/combinator/opt.dart new file mode 100644 index 00000000..3f31c995 --- /dev/null +++ b/packages/combinator/lib/src/combinator/opt.dart @@ -0,0 +1,57 @@ +part of lex.src.combinator; + +class _Opt extends Parser { + final Parser parser; + final bool backtrack; + + _Opt(this.parser, this.backtrack); + + @override + ParseResult __parse(ParseArgs args) { + var replay = args.scanner.position; + var result = parser._parse(args.increaseDepth()); + + if (!result.successful) args.scanner.position = replay; + + return result.change(parser: this, successful: true); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('optional (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} + +class _ListOpt extends ListParser { + final ListParser parser; + final bool backtrack; + + _ListOpt(this.parser, this.backtrack); + + @override + ParseResult> __parse(ParseArgs args) { + var replay = args.scanner.position; + var result = parser._parse(args.increaseDepth()); + + if (!result.successful) args.scanner.position = replay; + + return result.change(parser: this, successful: true); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('optional (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/recursion.dart b/packages/combinator/lib/src/combinator/recursion.dart new file mode 100644 index 00000000..faf7b5bf --- /dev/null +++ b/packages/combinator/lib/src/combinator/recursion.dart @@ -0,0 +1,142 @@ +part of lex.src.combinator; + +/* +/// Handles left recursion in a grammar using the Pratt algorithm. +class Recursion { + Iterable> prefix; + Map)> infix; + Map)> postfix; + + Recursion({this.prefix, this.infix, this.postfix}) { + prefix ??= []; + infix ??= {}; + postfix ??= {}; + } + + Parser precedence(int p) => new _Precedence(this, p); + + void stringify(CodeBuffer buffer) { + buffer + ..writeln('recursion (') + ..indent() + ..writeln('prefix(${prefix.length}') + ..writeln('infix(${infix.length}') + ..writeln('postfix(${postfix.length}') + ..outdent() + ..writeln(')'); + } +} + +class _Precedence extends Parser { + final Recursion r; + final int precedence; + + _Precedence(this.r, this.precedence); + + @override + ParseResult __parse(ParseArgs args) { + int replay = args.scanner.position; + var errors = []; + var start = args.scanner.state; + var reversedKeys = r.infix.keys.toList().reversed; + + for (var pre in r.prefix) { + var result = pre._parse(args.increaseDepth()), originalResult = result; + + if (!result.successful) { + if (pre is _Alt) errors.addAll(result.errors); + args.scanner.position = replay; + } else { + var left = result.value; + replay = args.scanner.position; + //print('${result.span.text}:\n' + scanner.emptySpan.highlight()); + + while (true) { + bool matched = false; + + //for (int i = 0; i < r.infix.length; i++) { + for (int i = r.infix.length - 1; i >= 0; i--) { + //var fix = r.infix.keys.elementAt(r.infix.length - i - 1); + var fix = reversedKeys.elementAt(i); + + if (i < precedence) continue; + + var result = fix._parse(args.increaseDepth()); + + if (!result.successful) { + if (fix is _Alt) errors.addAll(result.errors); + // If this is the last alternative and it failed, don't continue looping. + //if (true || i + 1 < r.infix.length) + args.scanner.position = replay; + } else { + //print('FOUND $fix when left was $left'); + //print('$i vs $precedence\n${originalResult.span.highlight()}'); + result = r.precedence(i)._parse(args.increaseDepth()); + + if (!result.successful) { + } else { + matched = false; + var old = left; + left = r.infix[fix](left, result.value, result); + print( + '$old $fix ${result.value} = $left\n${result.span.highlight()}'); + break; + } + } + } + + if (!matched) break; + } + + replay = args.scanner.position; + //print('f ${result.span.text}'); + + for (var post in r.postfix.keys) { + var result = pre._parse(args.increaseDepth()); + + if (!result.successful) { + if (post is _Alt) errors.addAll(result.errors); + args.scanner.position = replay; + } else { + left = r.infix[post](left, originalResult.value, result); + } + } + + if (!args.scanner.isDone) { + // If we're not done scanning, then we need some sort of guard to ensure the + // that this exact parser does not run again in the exact position. + } + return new ParseResult( + args.trampoline, + args.scanner, + this, + true, + errors, + value: left, + span: args.scanner.spanFrom(start), + ); + } + } + + return new ParseResult( + args.trampoline, + args.scanner, + this, + false, + errors, + span: args.scanner.spanFrom(start), + ); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('precedence($precedence) (') + ..indent(); + r.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} +*/ diff --git a/packages/combinator/lib/src/combinator/reduce.dart b/packages/combinator/lib/src/combinator/reduce.dart new file mode 100644 index 00000000..cb88f94b --- /dev/null +++ b/packages/combinator/lib/src/combinator/reduce.dart @@ -0,0 +1,45 @@ +part of lex.src.combinator; + +class _Reduce extends Parser { + final ListParser parser; + final T Function(T, T) combine; + + _Reduce(this.parser, this.combine); + + @override + ParseResult __parse(ParseArgs args) { + var result = parser._parse(args.increaseDepth()); + + if (!result.successful) + return new ParseResult( + args.trampoline, + args.scanner, + this, + false, + result.errors, + ); + + result = result.change( + value: result.value?.isNotEmpty == true ? result.value : []); + return new ParseResult( + args.trampoline, + args.scanner, + this, + result.successful, + [], + span: result.span, + value: result.value.isEmpty ? null : result.value.reduce(combine), + ); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('reduce($combine) (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/reference.dart b/packages/combinator/lib/src/combinator/reference.dart new file mode 100644 index 00000000..d46f5a6e --- /dev/null +++ b/packages/combinator/lib/src/combinator/reference.dart @@ -0,0 +1,38 @@ +part of lex.src.combinator; + +Reference reference() => new Reference._(); + +class Reference extends Parser { + Parser _parser; + bool printed = false; + + Reference._(); + + void set parser(Parser value) { + if (_parser != null) + throw new StateError( + 'There is already a parser assigned to this reference.'); + _parser = value; + } + + @override + ParseResult __parse(ParseArgs args) { + if (_parser == null) + throw new StateError('There is no parser assigned to this reference.'); + return _parser._parse(args); + } + + @override + ParseResult _parse(ParseArgs args) { + return _parser._parse(args); + } + + @override + void stringify(CodeBuffer buffer) { + if (_parser == null) + buffer.writeln('(undefined reference <$T>)'); + else if (!printed) _parser.stringify(buffer); + printed = true; + buffer.writeln('(previously printed reference)'); + } +} diff --git a/packages/combinator/lib/src/combinator/repeat.dart b/packages/combinator/lib/src/combinator/repeat.dart new file mode 100644 index 00000000..4f62b5e4 --- /dev/null +++ b/packages/combinator/lib/src/combinator/repeat.dart @@ -0,0 +1,84 @@ +part of lex.src.combinator; + +class _Repeat extends ListParser { + final Parser parser; + final int count; + final bool exact, backtrack; + final String tooFew, tooMany; + final SyntaxErrorSeverity severity; + + _Repeat(this.parser, this.count, this.exact, this.tooFew, this.tooMany, + this.backtrack, this.severity); + + @override + ParseResult> __parse(ParseArgs args) { + var errors = []; + var results = []; + var spans = []; + int success = 0, replay = args.scanner.position; + ParseResult result; + + do { + result = parser._parse(args.increaseDepth()); + if (result.successful) { + success++; + results.add(result.value); + replay = args.scanner.position; + } else if (backtrack) args.scanner.position = replay; + + if (result.span != null) spans.add(result.span); + } while (result.successful); + + if (success < count) { + errors.addAll(result.errors); + errors.add( + new SyntaxError( + severity, + tooFew ?? 'Expected at least $count occurence(s).', + result.span ?? args.scanner.emptySpan, + ), + ); + + if (backtrack) args.scanner.position = replay; + + return new ParseResult>( + args.trampoline, args.scanner, this, false, errors); + } else if (success > count && exact) { + if (backtrack) args.scanner.position = replay; + + return new ParseResult>( + args.trampoline, args.scanner, this, false, [ + new SyntaxError( + severity, + tooMany ?? 'Expected no more than $count occurence(s).', + result.span ?? args.scanner.emptySpan, + ), + ]); + } + + var span = spans.reduce((a, b) => a.expand(b)); + return new ParseResult>( + args.trampoline, + args.scanner, + this, + true, + [], + span: span, + value: results, + ); + } + + @override + void stringify(CodeBuffer buffer) { + var r = new StringBuffer('{$count'); + if (!exact) r.write(','); + r.write('}'); + buffer + ..writeln('repeat($r) (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/safe.dart b/packages/combinator/lib/src/combinator/safe.dart new file mode 100644 index 00000000..02127275 --- /dev/null +++ b/packages/combinator/lib/src/combinator/safe.dart @@ -0,0 +1,51 @@ +part of lex.src.combinator; + +class _Safe extends Parser { + final Parser parser; + final bool backtrack; + final String errorMessage; + final SyntaxErrorSeverity severity; + bool _triggered = false; + + _Safe(this.parser, this.backtrack, this.errorMessage, this.severity); + + @override + ParseResult __parse(ParseArgs args) { + var replay = args.scanner.position; + + try { + if (_triggered) throw null; + return parser._parse(args.increaseDepth()); + } catch (_) { + _triggered = true; + if (backtrack) args.scanner.position = replay; + var errors = []; + + if (errorMessage != null) { + // TODO: Custom severity for all errors? + errors.add( + new SyntaxError( + severity, + errorMessage, + args.scanner.lastSpan ?? args.scanner.emptySpan, + ), + ); + } + + return new ParseResult( + args.trampoline, args.scanner, this, false, errors); + } + } + + @override + void stringify(CodeBuffer buffer) { + var t = _triggered ? 'triggered' : 'not triggered'; + buffer + ..writeln('safe($t) (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/to_list.dart b/packages/combinator/lib/src/combinator/to_list.dart new file mode 100644 index 00000000..eb54b5c4 --- /dev/null +++ b/packages/combinator/lib/src/combinator/to_list.dart @@ -0,0 +1,37 @@ +part of lex.src.combinator; + +class _ToList extends ListParser { + final Parser parser; + + _ToList(this.parser); + + @override + ParseResult> __parse(ParseArgs args) { + var result = parser._parse(args.increaseDepth()); + + if (result.value is List) { + return (result as ParseResult>).change(parser: this); + } + + return new ParseResult( + args.trampoline, + args.scanner, + this, + result.successful, + result.errors, + span: result.span, + value: [result.value], + ); + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('to list (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/combinator/util.dart b/packages/combinator/lib/src/combinator/util.dart new file mode 100644 index 00000000..7e0b56c4 --- /dev/null +++ b/packages/combinator/lib/src/combinator/util.dart @@ -0,0 +1,57 @@ +part of lex.src.combinator; + +/// A typed parser that parses a sequence of 2 values of different types. +Parser> tuple2(Parser a, Parser b) { + return chain([a, b]).map((r) { + return Tuple2(r.value[0] as A, r.value[1] as B); + }); +} + +/// A typed parser that parses a sequence of 3 values of different types. +Parser> tuple3(Parser a, Parser b, Parser c) { + return chain([a, b, c]).map((r) { + return Tuple3(r.value[0] as A, r.value[1] as B, r.value[2] as C); + }); +} + +/// A typed parser that parses a sequence of 4 values of different types. +Parser> tuple4( + Parser a, Parser b, Parser c, Parser d) { + return chain([a, b, c, d]).map((r) { + return Tuple4( + r.value[0] as A, r.value[1] as B, r.value[2] as C, r.value[3] as D); + }); +} + +/// A typed parser that parses a sequence of 5 values of different types. +Parser> tuple5( + Parser a, Parser b, Parser c, Parser d, Parser e) { + return chain([a, b, c, d, e]).map((r) { + return Tuple5(r.value[0] as A, r.value[1] as B, r.value[2] as C, + r.value[3] as D, r.value[4] as E); + }); +} + +/// A typed parser that parses a sequence of 6 values of different types. +Parser> tuple6(Parser a, + Parser b, Parser c, Parser d, Parser e, Parser f) { + return chain([a, b, c, d, e, f]).map((r) { + return Tuple6(r.value[0] as A, r.value[1] as B, r.value[2] as C, + r.value[3] as D, r.value[4] as E, r.value[5] as F); + }); +} + +/// A typed parser that parses a sequence of 7 values of different types. +Parser> tuple7( + Parser a, + Parser b, + Parser c, + Parser d, + Parser e, + Parser f, + Parser g) { + return chain([a, b, c, d, e, f, g]).map((r) { + return Tuple7(r.value[0] as A, r.value[1] as B, r.value[2] as C, + r.value[3] as D, r.value[4] as E, r.value[5] as F, r.value[6] as G); + }); +} diff --git a/packages/combinator/lib/src/combinator/value.dart b/packages/combinator/lib/src/combinator/value.dart new file mode 100644 index 00000000..b0612073 --- /dev/null +++ b/packages/combinator/lib/src/combinator/value.dart @@ -0,0 +1,25 @@ +part of lex.src.combinator; + +class _Value extends Parser { + final Parser parser; + final T Function(ParseResult) f; + + _Value(this.parser, this.f); + + @override + ParseResult __parse(ParseArgs args) { + var result = parser._parse(args.increaseDepth()).change(parser: this); + return result.successful ? result.change(value: f(result)) : result; + } + + @override + void stringify(CodeBuffer buffer) { + buffer + ..writeln('set value($f) (') + ..indent(); + parser.stringify(buffer); + buffer + ..outdent() + ..writeln(')'); + } +} diff --git a/packages/combinator/lib/src/error.dart b/packages/combinator/lib/src/error.dart new file mode 100644 index 00000000..e6b4de09 --- /dev/null +++ b/packages/combinator/lib/src/error.dart @@ -0,0 +1,23 @@ +import 'package:source_span/source_span.dart'; + +class SyntaxError implements Exception { + final SyntaxErrorSeverity severity; + final String message; + final FileSpan span; + String _toolString; + + SyntaxError(this.severity, this.message, this.span); + + String get toolString { + if (_toolString != null) return _toolString; + var type = severity == SyntaxErrorSeverity.warning ? 'warning' : 'error'; + return _toolString = '$type: ${span.start.toolString}: $message'; + } +} + +enum SyntaxErrorSeverity { + warning, + error, + info, + hint, +} diff --git a/packages/combinator/pubspec.yaml b/packages/combinator/pubspec.yaml new file mode 100644 index 00000000..122c162c --- /dev/null +++ b/packages/combinator/pubspec.yaml @@ -0,0 +1,20 @@ +name: combinator +version: 2.0.0 +description: Packrat parser combinators that support static typing, generics, file spans, memoization, and more. +author: Tobe O +homepage: https://github.com/thosakwe/combinator.git +publish_to: none +environment: + sdk: ">=2.10.0 <3.0.0" +dependencies: + code_buffer: + git: + url: https://github.com/dukefirehawk/angel.git + ref: sdk-2.12.x_nnbd + path: packages/code_buffer + matcher: ^0.12.0 + source_span: ^1.8.1 + string_scanner: ^1.1.0 + tuple: ^2.0.0 +dev_dependencies: + test: ^1.16.8 \ No newline at end of file diff --git a/packages/combinator/test/all.dart b/packages/combinator/test/all.dart new file mode 100644 index 00000000..bc3144cb --- /dev/null +++ b/packages/combinator/test/all.dart @@ -0,0 +1,12 @@ +import 'package:test/test.dart'; +import 'list_test.dart' as list; +import 'match_test.dart' as match; +import 'misc_test.dart' as misc; +import 'value_test.dart' as value; + +main() { + group('list', list.main); + group('match', match.main); + group('value', value.main); + misc.main(); +} diff --git a/packages/combinator/test/common.dart b/packages/combinator/test/common.dart new file mode 100644 index 00000000..71dc6333 --- /dev/null +++ b/packages/combinator/test/common.dart @@ -0,0 +1,3 @@ +import 'package:string_scanner/string_scanner.dart'; + +SpanScanner scan(String text) => new SpanScanner(text); diff --git a/packages/combinator/test/list_test.dart b/packages/combinator/test/list_test.dart new file mode 100644 index 00000000..06e7dd4b --- /dev/null +++ b/packages/combinator/test/list_test.dart @@ -0,0 +1,22 @@ +import 'package:combinator/combinator.dart'; +import 'package:test/test.dart'; +import 'common.dart'; + +main() { + var number = chain([ + match(new RegExp(r'[0-9]+')).value((r) => int.parse(r.span.text)), + match(',').opt(), + ]).first().cast(); + + var numbers = number.plus(); + + test('sort', () { + var parser = numbers.sort((a, b) => a.compareTo(b)); + expect(parser.parse(scan('21,2,3,34,20')).value, [2, 3, 20, 21, 34]); + }); + test('reduce', () { + var parser = numbers.reduce((a, b) => a + b); + expect(parser.parse(scan('21,2,3,34,20')).value, 80); + expect(parser.parse(scan('not numbers')).value, isNull); + }); +} diff --git a/packages/combinator/test/match_test.dart b/packages/combinator/test/match_test.dart new file mode 100644 index 00000000..60504d93 --- /dev/null +++ b/packages/combinator/test/match_test.dart @@ -0,0 +1,16 @@ +import 'package:combinator/combinator.dart'; +import 'package:test/test.dart'; +import 'common.dart'; + +main() { + test('match string', () { + expect(match('hello').parse(scan('hello world')).successful, isTrue); + }); + test('match start only', () { + expect(match('hello').parse(scan('goodbye hello')).successful, isFalse); + }); + + test('fail if no match', () { + expect(match('hello').parse(scan('world')).successful, isFalse); + }); +} diff --git a/packages/combinator/test/misc_test.dart b/packages/combinator/test/misc_test.dart new file mode 100644 index 00000000..aca09d50 --- /dev/null +++ b/packages/combinator/test/misc_test.dart @@ -0,0 +1,66 @@ +import 'package:combinator/combinator.dart'; +import 'package:matcher/matcher.dart'; +import 'package:test/test.dart'; +import 'common.dart'; + +main() { + test('advance', () { + var scanner = scan('hello world'); + + // Casted -> dynamic just for the sake of coverage. + var parser = match('he').forward(2).castDynamic(); + parser.parse(scanner); + expect(scanner.position, 4); + }); + + test('change', () { + var parser = match('hello').change((r) => r.change(value: 23)); + expect(parser.parse(scan('helloworld')).value, 23); + }); + + test('check', () { + var parser = match(new RegExp(r'[A-Za-z]+')) + .value((r) => r.span.length) + .check(greaterThan(3)); + expect(parser.parse(scan('helloworld')).successful, isTrue); + expect(parser.parse(scan('yo')).successful, isFalse); + }); + + test('map', () { + var parser = match(new RegExp(r'[A-Za-z]+')).map((r) => r.span.length); + expect(parser.parse(scan('hello')).value, 5); + }); + + test('negate', () { + var parser = match('hello').negate(errorMessage: 'world'); + expect(parser.parse(scan('goodbye world')).successful, isTrue); + expect(parser.parse(scan('hello world')).successful, isFalse); + expect(parser.parse(scan('hello world')).errors.first.message, 'world'); + }); + + group('opt', () { + var single = match('hello').opt(backtrack: true); + var list = match('hel').then(match('lo')).opt(); + + test('succeeds if present', () { + expect(single.parse(scan('hello')).successful, isTrue); + expect(list.parse(scan('hello')).successful, isTrue); + }); + + test('succeeds if not present', () { + expect(single.parse(scan('goodbye')).successful, isTrue); + expect(list.parse(scan('goodbye')).successful, isTrue); + }); + + test('backtracks if not present', () { + for (var parser in [single, list]) { + var scanner = scan('goodbye'); + var pos = scanner.position; + parser.parse(scanner); + expect(scanner.position, pos); + } + }); + }); + + test('safe', () {}); +} diff --git a/packages/combinator/test/recursion_test.dart b/packages/combinator/test/recursion_test.dart new file mode 100644 index 00000000..688f44d3 --- /dev/null +++ b/packages/combinator/test/recursion_test.dart @@ -0,0 +1,57 @@ +import 'package:combinator/combinator.dart'; +import 'package:string_scanner/string_scanner.dart'; +import 'package:test/test.dart'; + +void main() {} + +/* +void main() { + var number = match(new RegExp(r'-?[0-9]+(\.[0-9]+)?')) + .map((r) => num.parse(r.span.text)); + + var term = reference(); + + var r = new Recursion(); + + r.prefix = [number]; + + r.infix.addAll({ + match('*'): (l, r, _) => l * r, + match('/'): (l, r, _) => l / r, + match('+'): (l, r, _) => l + r, + match('-'): (l, r, _) => l - r, + + + match('-'): (l, r, _) => l - r, + match('+'): (l, r, _) => l + r, + match('/'): (l, r, _) => l / r, + match('*'): (l, r, _) => l * r, + + }); + + term.parser = r.precedence(0); + + num parse(String text) { + var scanner = new SpanScanner(text); + var result = term.parse(scanner); + print(result.span.highlight()); + return result.value; + } + + test('prefix', () { + expect(parse('24'), 24); + }); + + test('infix', () { + expect(parse('12/6'), 2); + expect(parse('24+23'), 47); + expect(parse('24-23'), 1); + expect(parse('4*3'), 12); + }); + + test('precedence', () { + expect(parse('2+3*5*2'), 15); + //expect(parse('2+3+5-2*2'), 15); + }); +} +*/ diff --git a/packages/combinator/test/value_test.dart b/packages/combinator/test/value_test.dart new file mode 100644 index 00000000..c987d10f --- /dev/null +++ b/packages/combinator/test/value_test.dart @@ -0,0 +1,15 @@ +import 'package:combinator/combinator.dart'; +import 'package:test/test.dart'; +import 'common.dart'; + +main() { + var parser = match('hello').value((r) => 'world'); + + test('sets value', () { + expect(parser.parse(scan('hello world')).value, 'world'); + }); + + test('no value if no match', () { + expect(parser.parse(scan('goodbye world')).value, isNull); + }); +} From d62a4aa7ac8bec3d7290cb53f5f244a763e320f5 Mon Sep 17 00:00:00 2001 From: thomashii Date: Thu, 18 Mar 2021 08:00:56 +0800 Subject: [PATCH 005/171] Updated to Nullsafety --- packages/combinator/example/basic_auth.dart | 16 +- packages/combinator/example/calculator.dart | 16 +- packages/combinator/example/delimiter.dart | 8 +- packages/combinator/example/json.dart | 12 +- packages/combinator/example/main.dart | 8 +- packages/combinator/example/query_string.dart | 14 +- packages/combinator/example/sexp.dart | 10 +- packages/combinator/example/tuple.dart | 4 +- .../lib/src/combinator/advance.dart | 2 +- .../combinator/lib/src/combinator/any.dart | 6 +- .../combinator/lib/src/combinator/cache.dart | 4 +- .../combinator/lib/src/combinator/cast.dart | 6 +- .../combinator/lib/src/combinator/chain.dart | 16 +- .../combinator/lib/src/combinator/check.dart | 4 +- .../lib/src/combinator/combinator.dart | 137 +++++++++--------- .../lib/src/combinator/compare.dart | 6 +- .../lib/src/combinator/fold_errors.dart | 2 +- .../combinator/lib/src/combinator/index.dart | 8 +- .../lib/src/combinator/longest.dart | 16 +- .../combinator/lib/src/combinator/map.dart | 4 +- .../combinator/lib/src/combinator/match.dart | 4 +- .../lib/src/combinator/max_depth.dart | 2 +- .../combinator/lib/src/combinator/negate.dart | 4 +- .../combinator/lib/src/combinator/opt.dart | 4 +- .../combinator/lib/src/combinator/reduce.dart | 4 +- .../lib/src/combinator/reference.dart | 12 +- .../combinator/lib/src/combinator/repeat.dart | 22 +-- .../combinator/lib/src/combinator/safe.dart | 6 +- .../lib/src/combinator/to_list.dart | 8 +- .../combinator/lib/src/combinator/util.dart | 30 ++-- .../combinator/lib/src/combinator/value.dart | 2 +- packages/combinator/lib/src/error.dart | 10 +- packages/combinator/pubspec.yaml | 2 +- packages/combinator/test/list_test.dart | 12 +- packages/combinator/test/match_test.dart | 6 +- packages/combinator/test/misc_test.dart | 29 ++-- packages/combinator/test/value_test.dart | 4 +- 37 files changed, 231 insertions(+), 229 deletions(-) diff --git a/packages/combinator/example/basic_auth.dart b/packages/combinator/example/basic_auth.dart index e6697590..9c64e7e0 100644 --- a/packages/combinator/example/basic_auth.dart +++ b/packages/combinator/example/basic_auth.dart @@ -10,7 +10,7 @@ import 'package:string_scanner/string_scanner.dart'; /// Namely, the `username` or `password` in `{username}:{password}`. final Parser string = match(new RegExp(r'[^:$]+'), errorMessage: 'Expected a string.') - .value((r) => r.span.text); + .value((r) => r.span!.text); /// Transforms `{username}:{password}` to `{"username": username, "password": password}`. final Parser> credentials = chain([ @@ -18,19 +18,19 @@ final Parser> credentials = chain([ match(':'), string.opt(), ]).map>( - (r) => {'username': r.value[0], 'password': r.value[2]}); + (r) => {'username': r.value![0], 'password': r.value![2]}); /// We can actually embed a parser within another parser. /// /// This is used here to BASE64URL-decode a string, and then /// parse the decoded string. -final Parser credentialString = match>( +final Parser credentialString = match?>( new RegExp(r'([^\n$]+)'), errorMessage: 'Expected a credential string.') .value((r) { - var decoded = utf8.decode(base64Url.decode(r.span.text)); + var decoded = utf8.decode(base64Url.decode(r.span!.text)); var scanner = new SpanScanner(decoded); - return credentials.parse(scanner).value; + return credentials.parse(scanner)!.value; }); final Parser basic = match('Basic').space(); @@ -40,14 +40,14 @@ final Parser basicAuth = basic.then(credentialString).index(1); void main() { while (true) { stdout.write('Enter a basic auth value: '); - var line = stdin.readLineSync(); + var line = stdin.readLineSync()!; var scanner = new SpanScanner(line, sourceUrl: 'stdin'); - var result = basicAuth.parse(scanner); + var result = basicAuth.parse(scanner)!; if (!result.successful) { for (var error in result.errors) { print(error.toolString); - print(error.span.highlight(color: true)); + print(error.span!.highlight(color: true)); } } else print(result.value); diff --git a/packages/combinator/example/calculator.dart b/packages/combinator/example/calculator.dart index 59a436ee..34e799ea 100644 --- a/packages/combinator/example/calculator.dart +++ b/packages/combinator/example/calculator.dart @@ -8,13 +8,13 @@ Parser calculatorGrammar() { var expr = reference(); var number = match(new RegExp(r'-?[0-9]+(\.[0-9]+)?')) - .value((r) => num.parse(r.span.text)); + .value((r) => num.parse(r.span!.text)); var hex = match(new RegExp(r'0x([A-Fa-f0-9]+)')) - .map((r) => int.parse(r.scanner.lastMatch[1], radix: 16)); + .map((r) => int.parse(r.scanner.lastMatch![1]!, radix: 16)); var binary = match(new RegExp(r'([0-1]+)b')) - .map((r) => int.parse(r.scanner.lastMatch[1], radix: 2)); + .map((r) => int.parse(r.scanner.lastMatch![1]!, radix: 2)); var alternatives = >[]; @@ -22,9 +22,9 @@ Parser calculatorGrammar() { alternatives.add( chain([ expr.space(), - match(op).space(), + match(op).space() as Parser, expr.space(), - ]).map((r) => f(r.value[0], r.value[2])), + ]).map((r) => f(r.value![0], r.value![2])), ); } @@ -55,14 +55,14 @@ void main() { while (true) { stdout.write('Enter an expression: '); - var line = stdin.readLineSync(); + var line = stdin.readLineSync()!; var scanner = new SpanScanner(line, sourceUrl: 'stdin'); - var result = calculator.parse(scanner); + var result = calculator.parse(scanner)!; if (!result.successful) { for (var error in result.errors) { stderr.writeln(error.toolString); - stderr.writeln(error.span.highlight(color: true)); + stderr.writeln(error.span!.highlight(color: true)); } } else print(result.value); diff --git a/packages/combinator/example/delimiter.dart b/packages/combinator/example/delimiter.dart index 0028accc..03213de0 100644 --- a/packages/combinator/example/delimiter.dart +++ b/packages/combinator/example/delimiter.dart @@ -3,7 +3,7 @@ import 'package:combinator/combinator.dart'; import 'package:string_scanner/string_scanner.dart'; final Parser id = - match(RegExp(r'[A-Za-z]+')).value((r) => r.span.text); + match(RegExp(r'[A-Za-z]+')).value((r) => r.span!.text); // We can use `separatedBy` to easily construct parser // that can be matched multiple times, separated by another @@ -13,14 +13,14 @@ final Parser id = main() { while (true) { stdout.write('Enter a string (ex "a,b,c"): '); - var line = stdin.readLineSync(); + var line = stdin.readLineSync()!; var scanner = new SpanScanner(line, sourceUrl: 'stdin'); - var result = id.separatedBy(match(',').space()).parse(scanner); + var result = id.separatedBy(match(',').space()).parse(scanner)!; if (!result.successful) { for (var error in result.errors) { print(error.toolString); - print(error.span.highlight(color: true)); + print(error.span!.highlight(color: true)); } } else print(result.value); diff --git a/packages/combinator/example/json.dart b/packages/combinator/example/json.dart index 10dd0612..34d1e838 100644 --- a/packages/combinator/example/json.dart +++ b/packages/combinator/example/json.dart @@ -9,13 +9,13 @@ Parser jsonGrammar() { var number = match(new RegExp(r'-?[0-9]+(\.[0-9]+)?'), errorMessage: 'Expected a number.') .value( - (r) => num.parse(r.span.text), + (r) => num.parse(r.span!.text), ); // Parse a string (no escapes supported, because lazy). var string = match(new RegExp(r'"[^"]*"'), errorMessage: 'Expected a string.').value( - (r) => r.span.text.substring(1, r.span.text.length - 1), + (r) => r.span!.text.substring(1, r.span!.text.length - 1), ); // Parse an array @@ -29,7 +29,7 @@ Parser jsonGrammar() { string.space(), match(':').space(), expr.error(errorMessage: 'Missing expression.'), - ]).castDynamic().cast().value((r) => {r.value[0]: r.value[2]}); + ]).castDynamic().cast().value((r) => {r.value![0]: r.value![2]}); // Parse an object. var object = keyValuePair @@ -55,14 +55,14 @@ main() { while (true) { stdout.write('Enter some JSON: '); - var line = stdin.readLineSync(); + var line = stdin.readLineSync()!; var scanner = new SpanScanner(line, sourceUrl: 'stdin'); - var result = JSON.parse(scanner); + var result = JSON.parse(scanner)!; if (!result.successful) { for (var error in result.errors) { print(error.toolString); - print(error.span.highlight(color: true)); + print(error.span!.highlight(color: true)); } } else print(result.value); diff --git a/packages/combinator/example/main.dart b/packages/combinator/example/main.dart index 96dfc233..c6c15649 100644 --- a/packages/combinator/example/main.dart +++ b/packages/combinator/example/main.dart @@ -17,19 +17,19 @@ final Parser decimal = ( // digits, (dot, digits)? final Parser number = // (minus.opt() & decimal) // minus?, decimal - .map((r) => num.parse(r.span.text)); + .map((r) => num.parse(r.span!.text)); main() { while (true) { stdout.write('Enter a number: '); - var line = stdin.readLineSync(); + var line = stdin.readLineSync()!; var scanner = new SpanScanner(line, sourceUrl: 'stdin'); - var result = number.parse(scanner); + var result = number.parse(scanner)!; if (!result.successful) { for (var error in result.errors) { stderr.writeln(error.toolString); - stderr.writeln(error.span.highlight(color: true)); + stderr.writeln(error.span!.highlight(color: true)); } } else print(result.value); diff --git a/packages/combinator/example/query_string.dart b/packages/combinator/example/query_string.dart index a1f8a3f2..87d8e8e7 100644 --- a/packages/combinator/example/query_string.dart +++ b/packages/combinator/example/query_string.dart @@ -6,9 +6,9 @@ import 'package:string_scanner/string_scanner.dart'; final Parser key = match(RegExp(r'[^=&\n]+'), errorMessage: 'Missing k/v') - .value((r) => r.span.text); + .value((r) => r.span!.text); -final Parser value = key.map((r) => Uri.decodeQueryComponent(r.value)); +final Parser value = key.map((r) => Uri.decodeQueryComponent(r.value!)); final Parser pair = chain([ key, @@ -16,27 +16,27 @@ final Parser pair = chain([ value, ]).map((r) { return { - r.value[0]: r.value[2], + r.value![0]: r.value![2], }; }); final Parser pairs = pair .separatedBy(match(r'&')) - .map((r) => r.value.reduce((a, b) => a..addAll(b))); + .map((r) => r.value!.reduce((a, b) => a..addAll(b))); final Parser queryString = pairs.opt(); main() { while (true) { stdout.write('Enter a query string: '); - var line = stdin.readLineSync(); + var line = stdin.readLineSync()!; var scanner = new SpanScanner(line, sourceUrl: 'stdin'); - var result = pairs.parse(scanner); + var result = pairs.parse(scanner)!; if (!result.successful) { for (var error in result.errors) { print(error.toolString); - print(error.span.highlight(color: true)); + print(error.span!.highlight(color: true)); } } else print(result.value); diff --git a/packages/combinator/example/sexp.dart b/packages/combinator/example/sexp.dart index 5e7d4083..1c8add58 100644 --- a/packages/combinator/example/sexp.dart +++ b/packages/combinator/example/sexp.dart @@ -27,21 +27,21 @@ void main() { var number = match(new RegExp(r'[0-9]+(\.[0-9]+)?'), errorMessage: 'Expected a number.') - .map((r) => num.parse(r.span.text)); + .map((r) => num.parse(r.span!.text)); var id = match( new RegExp( r'[A-Za-z_!\\$",\\+-\\./:;\\?<>%&\\*@\[\]\\{\}\\|`\\^~][A-Za-z0-9_!\\$",\\+-\\./:;\\?<>%&\*@\[\]\\{\}\\|`\\^~]*'), errorMessage: 'Expected an ID') .map((r) => - symbols[r.span.text] ??= throw "Undefined symbol: '${r.span.text}'"); + symbols[r.span!.text] ??= throw "Undefined symbol: '${r.span!.text}'"); var atom = number.castDynamic().or(id); var list = expr.space().times(2, exact: false).map((r) { try { var out = []; - var q = new Queue.from(r.value.reversed); + var q = new Queue.from(r.value!.reversed); while (q.isNotEmpty) { var current = q.removeFirst(); @@ -69,8 +69,8 @@ void main() { while (true) { stdout.write('> '); - var line = stdin.readLineSync(); - var result = expr.parse(new SpanScanner(line)); + var line = stdin.readLineSync()!; + var result = expr.parse(new SpanScanner(line))!; if (result.errors.isNotEmpty) { for (var error in result.errors) { diff --git a/packages/combinator/example/tuple.dart b/packages/combinator/example/tuple.dart index 453c7e61..b92f4262 100644 --- a/packages/combinator/example/tuple.dart +++ b/packages/combinator/example/tuple.dart @@ -2,7 +2,7 @@ import 'package:combinator/combinator.dart'; import 'package:string_scanner/string_scanner.dart'; void main() { - var pub = match('pub').map((r) => r.span.text).space(); + var pub = match('pub').map((r) => r.span!.text).space(); var dart = match('dart').map((r) => 24).space(); var lang = match('lang').map((r) => true).space(); @@ -10,5 +10,5 @@ void main() { var grammar = tuple3(pub, dart, lang); var scanner = SpanScanner('pub dart lang'); - print(grammar.parse(scanner).value); + print(grammar.parse(scanner)!.value); } diff --git a/packages/combinator/lib/src/combinator/advance.dart b/packages/combinator/lib/src/combinator/advance.dart index 01506dc2..49fcdb88 100644 --- a/packages/combinator/lib/src/combinator/advance.dart +++ b/packages/combinator/lib/src/combinator/advance.dart @@ -8,7 +8,7 @@ class _Advance extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth()).change(parser: this); + var result = parser._parse(args.increaseDepth())!.change(parser: this); if (result.successful) args.scanner.position += amount; return result; } diff --git a/packages/combinator/lib/src/combinator/any.dart b/packages/combinator/lib/src/combinator/any.dart index 618d2b58..0c16a81d 100644 --- a/packages/combinator/lib/src/combinator/any.dart +++ b/packages/combinator/lib/src/combinator/any.dart @@ -7,7 +7,7 @@ part of lex.src.combinator; /// You can provide a custom [errorMessage]. You can set it to `false` to not /// generate any error at all. Parser any(Iterable> parsers, - {bool backtrack: true, errorMessage, SyntaxErrorSeverity severity}) { + {bool backtrack: true, errorMessage, SyntaxErrorSeverity? severity}) { return new _Any(parsers, backtrack != false, errorMessage, severity ?? SyntaxErrorSeverity.error); } @@ -33,7 +33,7 @@ class _Any extends Parser { int replay = args.scanner.position; for (var parser in inactive) { - var result = parser._parse(args.increaseDepth()); + var result = parser._parse(args.increaseDepth())!; if (result.successful) return result; @@ -58,7 +58,7 @@ class _Any extends Parser { } @override - ParseResult __parse(ParseArgs args) { + ParseResult? __parse(ParseArgs args) { // Never called return null; } diff --git a/packages/combinator/lib/src/combinator/cache.dart b/packages/combinator/lib/src/combinator/cache.dart index b07b38e0..f57569f0 100644 --- a/packages/combinator/lib/src/combinator/cache.dart +++ b/packages/combinator/lib/src/combinator/cache.dart @@ -1,7 +1,7 @@ part of lex.src.combinator; class _Cache extends Parser { - final Map> _cache = {}; + final Map?> _cache = {}; final Parser parser; _Cache(this.parser); @@ -10,7 +10,7 @@ class _Cache extends Parser { ParseResult __parse(ParseArgs args) { return _cache.putIfAbsent(args.scanner.position, () { return parser._parse(args.increaseDepth()); - }).change(parser: this); + })!.change(parser: this); } @override diff --git a/packages/combinator/lib/src/combinator/cast.dart b/packages/combinator/lib/src/combinator/cast.dart index b1e91d0e..6bd44f80 100644 --- a/packages/combinator/lib/src/combinator/cast.dart +++ b/packages/combinator/lib/src/combinator/cast.dart @@ -7,7 +7,7 @@ class _Cast extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth()); + var result = parser._parse(args.increaseDepth())!; return new ParseResult( args.trampoline, args.scanner, @@ -15,7 +15,7 @@ class _Cast extends Parser { result.successful, result.errors, span: result.span, - value: result.value as U, + value: result.value as U?, ); } @@ -38,7 +38,7 @@ class _CastDynamic extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth()); + var result = parser._parse(args.increaseDepth())!; return new ParseResult( args.trampoline, args.scanner, diff --git a/packages/combinator/lib/src/combinator/chain.dart b/packages/combinator/lib/src/combinator/chain.dart index 3925aed4..8e0552c8 100644 --- a/packages/combinator/lib/src/combinator/chain.dart +++ b/packages/combinator/lib/src/combinator/chain.dart @@ -4,21 +4,21 @@ part of lex.src.combinator; /// /// If [failFast] is `true` (default), then the first failure to parse will abort the parse. ListParser chain(Iterable> parsers, - {bool failFast: true, SyntaxErrorSeverity severity}) { + {bool failFast: true, SyntaxErrorSeverity? severity}) { return new _Chain( parsers, failFast != false, severity ?? SyntaxErrorSeverity.error); } class _Alt extends Parser { final Parser parser; - final String errorMessage; + final String? errorMessage; final SyntaxErrorSeverity severity; _Alt(this.parser, this.errorMessage, this.severity); @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth()); + var result = parser._parse(args.increaseDepth())!; return result.successful ? result : result.addErrors([ @@ -43,12 +43,12 @@ class _Chain extends ListParser { @override ParseResult> __parse(ParseArgs args) { var errors = []; - var results = []; - var spans = []; + var results = []; + var spans = []; bool successful = true; for (var parser in parsers) { - var result = parser._parse(args.increaseDepth()); + var result = parser._parse(args.increaseDepth())!; if (!result.successful) { if (parser is _Alt) errors.addAll(result.errors); @@ -66,10 +66,10 @@ class _Chain extends ListParser { if (result.span != null) spans.add(result.span); } - FileSpan span; + FileSpan? span; if (spans.isNotEmpty) { - span = spans.reduce((a, b) => a.expand(b)); + span = spans.reduce((a, b) => a!.expand(b!)); } return new ParseResult>( diff --git a/packages/combinator/lib/src/combinator/check.dart b/packages/combinator/lib/src/combinator/check.dart index 1f2159f0..58416040 100644 --- a/packages/combinator/lib/src/combinator/check.dart +++ b/packages/combinator/lib/src/combinator/check.dart @@ -3,7 +3,7 @@ part of lex.src.combinator; class _Check extends Parser { final Parser parser; final Matcher matcher; - final String errorMessage; + final String? errorMessage; final SyntaxErrorSeverity severity; _Check(this.parser, this.matcher, this.errorMessage, this.severity); @@ -11,7 +11,7 @@ class _Check extends Parser { @override ParseResult __parse(ParseArgs args) { var matchState = {}; - var result = parser._parse(args.increaseDepth()).change(parser: this); + var result = parser._parse(args.increaseDepth())!.change(parser: this); if (!result.successful) return result; else if (!matcher.matches(result.value, matchState)) { diff --git a/packages/combinator/lib/src/combinator/combinator.dart b/packages/combinator/lib/src/combinator/combinator.dart index 2b083a25..3b6d9b14 100644 --- a/packages/combinator/lib/src/combinator/combinator.dart +++ b/packages/combinator/lib/src/combinator/combinator.dart @@ -62,21 +62,21 @@ class ParseArgs { ParseArgs(this.trampoline, this.scanner, this.depth); - ParseArgs increaseDepth() => new ParseArgs(trampoline, scanner, depth + 1); + ParseArgs increaseDepth() => ParseArgs(trampoline, scanner, depth + 1); } /// A parser combinator, which can parse very complicated grammars in a manageable manner. abstract class Parser { - ParseResult __parse(ParseArgs args); + ParseResult? __parse(ParseArgs args); - ParseResult _parse(ParseArgs args) { + ParseResult? _parse(ParseArgs args) { var pos = args.scanner.position; if (args.trampoline.hasMemoized(this, pos)) return args.trampoline.getMemoized(this, pos); if (args.trampoline.isActive(this, pos)) - return new ParseResult(args.trampoline, args.scanner, this, false, []); + return ParseResult(args.trampoline, args.scanner, this, false, []); args.trampoline.enter(this, pos); var result = __parse(args); @@ -86,79 +86,80 @@ abstract class Parser { } /// Parses text from a [SpanScanner]. - ParseResult parse(SpanScanner scanner, [int depth = 1]) { - var args = new ParseArgs(new Trampoline(), scanner, depth); + ParseResult? parse(SpanScanner scanner, [int depth = 1]) { + var args = ParseArgs(Trampoline(), scanner, depth); return _parse(args); } /// Skips forward a certain amount of steps after parsing, if it was successful. - Parser forward(int amount) => new _Advance(this, amount); + Parser forward(int amount) => _Advance(this, amount); /// Moves backward a certain amount of steps after parsing, if it was successful. - Parser back(int amount) => new _Advance(this, amount * -1); + Parser back(int amount) => _Advance(this, amount * -1); /// Casts this parser to produce [U] objects. - Parser cast() => new _Cast(this); + Parser cast() => _Cast(this); /// Casts this parser to produce [dynamic] objects. - Parser castDynamic() => new _CastDynamic(this); + Parser castDynamic() => _CastDynamic(this); + // TODO: Type issue /// Runs the given function, which changes the returned [ParseResult] into one relating to a [U] object. - Parser change(ParseResult Function(ParseResult) f) { - return new _Change(this, f); + Parser change(ParseResult Function(ParseResult?) f) { + return _Change(this, f); } /// Validates the parse result against a [Matcher]. /// /// You can provide a custom [errorMessage]. Parser check(Matcher matcher, - {String errorMessage, SyntaxErrorSeverity severity}) => - new _Check( + {String? errorMessage, SyntaxErrorSeverity? severity}) => + _Check( this, matcher, errorMessage, severity ?? SyntaxErrorSeverity.error); /// Binds an [errorMessage] to a copy of this parser. - Parser error({String errorMessage, SyntaxErrorSeverity severity}) => - new _Alt(this, errorMessage, severity ?? SyntaxErrorSeverity.error); + Parser error({String? errorMessage, SyntaxErrorSeverity? severity}) => + _Alt(this, errorMessage, severity ?? SyntaxErrorSeverity.error); /// Removes multiple errors that occur in the same spot; this can reduce noise in parser output. - Parser foldErrors({bool equal(SyntaxError a, SyntaxError b)}) { - equal ??= (b, e) => b.span.start.offset == e.span.start.offset; - return new _FoldErrors(this, equal); + Parser foldErrors({bool equal(SyntaxError a, SyntaxError b)?}) { + equal ??= (b, e) => b.span!.start.offset == e.span!.start.offset; + return _FoldErrors(this, equal); } /// Transforms the parse result using a unary function. Parser map(U Function(ParseResult) f) { - return new _Map(this, f); + return _Map(this, f); } /// Prevents recursion past a certain [depth], preventing stack overflow errors. - Parser maxDepth(int depth) => new _MaxDepth(this, depth); + Parser maxDepth(int depth) => _MaxDepth(this, depth); Parser operator ~() => negate(); /// Ensures this pattern is not matched. /// /// You can provide an [errorMessage]. - Parser negate({String errorMessage, SyntaxErrorSeverity severity}) => - new _Negate(this, errorMessage, severity ?? SyntaxErrorSeverity.error); + Parser negate({String? errorMessage, SyntaxErrorSeverity? severity}) => + _Negate(this, errorMessage, severity ?? SyntaxErrorSeverity.error); /// Caches the results of parse attempts at various locations within the source text. /// /// Use this to prevent excessive recursion. - Parser cache() => new _Cache(this); + Parser cache() => _Cache(this); Parser operator &(Parser other) => and(other); /// Consumes `this` and another parser, but only considers the result of `this` parser. Parser and(Parser other) => then(other).change((r) { - return new ParseResult( - r.trampoline, + return ParseResult( + r!.trampoline, r.scanner, this, r.successful, r.errors, span: r.span, - value: (r.value != null ? r.value[0] : r.value) as T, + value: (r.value != null ? r.value![0] : r.value) as T?, ); }); @@ -168,16 +169,16 @@ abstract class Parser { Parser or(Parser other) => any([this, other]); /// Parses this sequence one or more times. - ListParser plus() => times(1, exact: false); + ListParser plus() => times(1, exact: false); /// Safely escapes this parser when an error occurs. /// /// The generated parser only runs once; repeated uses always exit eagerly. Parser safe( {bool backtrack: true, - String errorMessage, - SyntaxErrorSeverity severity}) => - new _Safe( + String? errorMessage, + SyntaxErrorSeverity? severity}) => + _Safe( this, backtrack, errorMessage, severity ?? SyntaxErrorSeverity.error); Parser> separatedByComma() => @@ -190,18 +191,18 @@ abstract class Parser { var suffix = other.then(this).index(1).cast(); return this.then(suffix.star()).map((r) { var preceding = - r.value.isEmpty ? [] : (r.value[0] == null ? [] : [r.value[0]]); - var out = new List.from(preceding); - if (r.value[1] != null) out.addAll(r.value[1] as Iterable); + r.value!.isEmpty ? [] : (r.value![0] == null ? [] : [r.value![0]]); + var out = List.from(preceding); + if (r.value![1] != null) out.addAll(r.value![1] as Iterable); return out; }); } - Parser surroundedByCurlyBraces({T defaultValue}) => opt() + Parser surroundedByCurlyBraces({T? defaultValue}) => opt() .surroundedBy(match('{').space(), match('}').space()) .map((r) => r.value ?? defaultValue); - Parser surroundedBySquareBrackets({T defaultValue}) => opt() + Parser surroundedBySquareBrackets({T? defaultValue}) => opt() .surroundedBy(match('[').space(), match(']').space()) .map((r) => r.value ?? defaultValue); @@ -209,7 +210,7 @@ abstract class Parser { /// /// If no [right] is provided, it expects to see the same pattern on both sides. /// Use this parse things like parenthesized expressions, arrays, etc. - Parser surroundedBy(Parser left, [Parser right]) { + Parser surroundedBy(Parser left, [Parser? right]) { return chain([ left, this, @@ -227,17 +228,17 @@ abstract class Parser { surroundedBy(match('(').space(), match(')').space()); /// Consumes any trailing whitespace. - Parser space() => trail(new RegExp(r'[ \n\r\t]+')); + Parser space() => trail(RegExp(r'[ \n\r\t]+')); /// Consumes 0 or more instance(s) of this parser. - ListParser star({bool backtrack: true}) => + ListParser star({bool backtrack: true}) => times(1, exact: false, backtrack: backtrack).opt(); /// Shortcut for [chain]-ing two parsers together. ListParser then(Parser other) => chain([this, other]); /// Casts this instance into a [ListParser]. - ListParser toList() => new _ToList(this); + ListParser toList() => _ToList(this); /// Consumes and ignores any trailing occurrences of [pattern]. Parser trail(Pattern pattern) => @@ -249,13 +250,13 @@ abstract class Parser { /// an infinite amount of occurrences after the specified [count]. /// /// You can provide custom error messages for when there are [tooFew] or [tooMany] occurrences. - ListParser times(int count, + ListParser times(int count, {bool exact: true, - String tooFew, - String tooMany, + String? tooFew, + String? tooMany, bool backtrack: true, - SyntaxErrorSeverity severity}) { - return new _Repeat(this, count, exact, tooFew, tooMany, backtrack, + SyntaxErrorSeverity? severity}) { + return _Repeat(this, count, exact, tooFew, tooMany, backtrack, severity ?? SyntaxErrorSeverity.error); } @@ -263,11 +264,11 @@ abstract class Parser { /// /// If [backtrack] is `true` (default), then a failed parse will not /// modify the scanner state. - Parser opt({bool backtrack: true}) => new _Opt(this, backtrack); + Parser opt({bool backtrack: true}) => _Opt(this, backtrack); /// Sets the value of the [ParseResult]. Parser value(T Function(ParseResult) f) { - return new _Value(this, f); + return _Value(this, f); } /// Prints a representation of this parser, ideally without causing a stack overflow. @@ -280,26 +281,26 @@ abstract class ListParser extends Parser> { Parser first() => index(0); /// Modifies this parser to only return the value at the given index [i]. - Parser index(int i) => new _Index(this, i); + Parser index(int i) => _Index(this, i); /// Shortcut for calling [index] with the greatest-possible index. Parser last() => index(-1); /// Modifies this parser to call `List.reduce` on the parsed values. - Parser reduce(T Function(T, T) combine) => new _Reduce(this, combine); + Parser reduce(T Function(T, T) combine) => _Reduce(this, combine); /// Sorts the parsed values, using the given [Comparator]. - ListParser sort(Comparator compare) => new _Compare(this, compare); + ListParser sort(Comparator compare) => _Compare(this, compare); @override - ListParser opt({bool backtrack: true}) => new _ListOpt(this, backtrack); + ListParser opt({bool backtrack: true}) => _ListOpt(this, backtrack); /// Modifies this parser, returning only the values that match a predicate. Parser> where(bool Function(T) f) => - map>((r) => r.value.where(f).toList()); + map>((r) => r.value!.where(f).toList()); /// Condenses a [ListParser] into having a value of the combined span's text. - Parser flatten() => map((r) => r.span.text); + Parser flatten() => map((r) => r.span!.text); } /// Prevents stack overflow in recursive parsers. @@ -313,32 +314,32 @@ class Trampoline { } ParseResult getMemoized(Parser parser, int position) { - return _memo[parser].firstWhere((t) => t.item1 == position).item2 + return _memo[parser]!.firstWhere((t) => t.item1 == position).item2 as ParseResult; } - void memoize(Parser parser, int position, ParseResult result) { + void memoize(Parser parser, int position, ParseResult? result) { if (result != null) { var list = _memo.putIfAbsent(parser, () => []); - var tuple = new Tuple2(position, result); + var tuple = Tuple2(position, result); if (!list.contains(tuple)) list.add(tuple); } } bool isActive(Parser parser, int position) { if (!_active.containsKey(parser)) return false; - var q = _active[parser]; + var q = _active[parser]!; if (q.isEmpty) return false; //return q.contains(position); return q.first == position; } void enter(Parser parser, int position) { - _active.putIfAbsent(parser, () => new Queue()).addFirst(position); + _active.putIfAbsent(parser, () => Queue()).addFirst(position); } void exit(Parser parser) { - if (_active.containsKey(parser)) _active[parser].removeFirst(); + if (_active.containsKey(parser)) _active[parser]!.removeFirst(); } } @@ -347,8 +348,8 @@ class ParseResult { final Parser parser; final bool successful; final Iterable errors; - final FileSpan span; - final T value; + final FileSpan? span; + final T? value; final SpanScanner scanner; final Trampoline trampoline; @@ -357,12 +358,12 @@ class ParseResult { {this.span, this.value}); ParseResult change( - {Parser parser, - bool successful, - Iterable errors, - FileSpan span, - T value}) { - return new ParseResult( + {Parser? parser, + bool? successful, + Iterable? errors, + FileSpan? span, + T? value}) { + return ParseResult( trampoline, scanner, parser ?? this.parser, @@ -375,7 +376,7 @@ class ParseResult { ParseResult addErrors(Iterable errors) { return change( - errors: new List.from(this.errors)..addAll(errors), + errors: List.from(this.errors)..addAll(errors), ); } } diff --git a/packages/combinator/lib/src/combinator/compare.dart b/packages/combinator/lib/src/combinator/compare.dart index 99447ac1..49a39701 100644 --- a/packages/combinator/lib/src/combinator/compare.dart +++ b/packages/combinator/lib/src/combinator/compare.dart @@ -8,12 +8,12 @@ class _Compare extends ListParser { @override ParseResult> __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth()); + ParseResult> result = parser._parse(args.increaseDepth())!; if (!result.successful) return result; result = result.change( value: result.value?.isNotEmpty == true ? result.value : []); - result = result.change(value: new List.from(result.value)); + result = result.change(value: new List.from(result.value!)); return new ParseResult>( args.trampoline, args.scanner, @@ -21,7 +21,7 @@ class _Compare extends ListParser { true, [], span: result.span, - value: result.value..sort(compare), + value: result.value?..sort(compare), ); } diff --git a/packages/combinator/lib/src/combinator/fold_errors.dart b/packages/combinator/lib/src/combinator/fold_errors.dart index 6d15c69a..5b838e6f 100644 --- a/packages/combinator/lib/src/combinator/fold_errors.dart +++ b/packages/combinator/lib/src/combinator/fold_errors.dart @@ -8,7 +8,7 @@ class _FoldErrors extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth()).change(parser: this); + var result = parser._parse(args.increaseDepth())!.change(parser: this); var errors = result.errors.fold>([], (out, e) { if (!out.any((b) => equal(e, b))) out.add(e); return out; diff --git a/packages/combinator/lib/src/combinator/index.dart b/packages/combinator/lib/src/combinator/index.dart index 0a57c3d7..a3791333 100644 --- a/packages/combinator/lib/src/combinator/index.dart +++ b/packages/combinator/lib/src/combinator/index.dart @@ -8,11 +8,11 @@ class _Index extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth()); - Object value; + ParseResult> result = parser._parse(args.increaseDepth())!; + Object? value; if (result.successful) - value = index == -1 ? result.value.last : result.value.elementAt(index); + value = index == -1 ? result.value!.last : result.value!.elementAt(index); return new ParseResult( args.trampoline, @@ -21,7 +21,7 @@ class _Index extends Parser { result.successful, result.errors, span: result.span, - value: value as T, + value: value as T?, ); } diff --git a/packages/combinator/lib/src/combinator/longest.dart b/packages/combinator/lib/src/combinator/longest.dart index dec48df9..ae00c2c8 100644 --- a/packages/combinator/lib/src/combinator/longest.dart +++ b/packages/combinator/lib/src/combinator/longest.dart @@ -4,14 +4,14 @@ part of lex.src.combinator; /// /// You can provide a custom [errorMessage]. Parser longest(Iterable> parsers, - {Object errorMessage, SyntaxErrorSeverity severity}) { + {Object? errorMessage, SyntaxErrorSeverity? severity}) { return new _Longest( parsers, errorMessage, severity ?? SyntaxErrorSeverity.error); } class _Longest extends Parser { final Iterable> parsers; - final Object errorMessage; + final Object? errorMessage; final SyntaxErrorSeverity severity; _Longest(this.parsers, this.errorMessage, this.severity); @@ -31,7 +31,7 @@ class _Longest extends Parser { var results = >[]; for (var parser in inactive) { - var result = parser._parse(args.increaseDepth()); + var result = parser._parse(args.increaseDepth())!; if (result.successful && result.span != null) results.add(result); @@ -41,8 +41,8 @@ class _Longest extends Parser { } if (results.isNotEmpty) { - results.sort((a, b) => b.span.length.compareTo(a.span.length)); - args.scanner.scan(results.first.span.text); + results.sort((a, b) => b.span!.length.compareTo(a.span!.length)); + args.scanner.scan(results.first.span!.text); return results.first; } @@ -66,7 +66,7 @@ class _Longest extends Parser { var results = >[]; for (var parser in parsers) { - var result = parser._parse(args.increaseDepth()); + var result = parser._parse(args.increaseDepth())!; if (result.successful) results.add(result); @@ -76,8 +76,8 @@ class _Longest extends Parser { } if (results.isNotEmpty) { - results.sort((a, b) => b.span.length.compareTo(a.span.length)); - args.scanner.scan(results.first.span.text); + results.sort((a, b) => b.span!.length.compareTo(a.span!.length)); + args.scanner.scan(results.first.span!.text); return results.first; } diff --git a/packages/combinator/lib/src/combinator/map.dart b/packages/combinator/lib/src/combinator/map.dart index b1e210a3..37acddd5 100644 --- a/packages/combinator/lib/src/combinator/map.dart +++ b/packages/combinator/lib/src/combinator/map.dart @@ -8,7 +8,7 @@ class _Map extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth()); + var result = parser._parse(args.increaseDepth())!; return new ParseResult( args.trampoline, args.scanner, @@ -34,7 +34,7 @@ class _Map extends Parser { class _Change extends Parser { final Parser parser; - final ParseResult Function(ParseResult) f; + final ParseResult Function(ParseResult?) f; _Change(this.parser, this.f); diff --git a/packages/combinator/lib/src/combinator/match.dart b/packages/combinator/lib/src/combinator/match.dart index 4ebfc1c6..e533a503 100644 --- a/packages/combinator/lib/src/combinator/match.dart +++ b/packages/combinator/lib/src/combinator/match.dart @@ -2,12 +2,12 @@ part of lex.src.combinator; /// Expects to match a given [pattern]. If it is not matched, you can provide a custom [errorMessage]. Parser match(Pattern pattern, - {String errorMessage, SyntaxErrorSeverity severity}) => + {String? errorMessage, SyntaxErrorSeverity? severity}) => new _Match(pattern, errorMessage, severity ?? SyntaxErrorSeverity.error); class _Match extends Parser { final Pattern pattern; - final String errorMessage; + final String? errorMessage; final SyntaxErrorSeverity severity; _Match(this.pattern, this.errorMessage, this.severity); diff --git a/packages/combinator/lib/src/combinator/max_depth.dart b/packages/combinator/lib/src/combinator/max_depth.dart index 1c28a9d2..f9054132 100644 --- a/packages/combinator/lib/src/combinator/max_depth.dart +++ b/packages/combinator/lib/src/combinator/max_depth.dart @@ -7,7 +7,7 @@ class _MaxDepth extends Parser { _MaxDepth(this.parser, this.cap); @override - ParseResult __parse(ParseArgs args) { + ParseResult? __parse(ParseArgs args) { if (args.depth > cap) { return new ParseResult(args.trampoline, args.scanner, this, false, []); } diff --git a/packages/combinator/lib/src/combinator/negate.dart b/packages/combinator/lib/src/combinator/negate.dart index db255afc..30a8c377 100644 --- a/packages/combinator/lib/src/combinator/negate.dart +++ b/packages/combinator/lib/src/combinator/negate.dart @@ -2,14 +2,14 @@ part of lex.src.combinator; class _Negate extends Parser { final Parser parser; - final String errorMessage; + final String? errorMessage; final SyntaxErrorSeverity severity; _Negate(this.parser, this.errorMessage, this.severity); @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth()).change(parser: this); + var result = parser._parse(args.increaseDepth())!.change(parser: this); if (!result.successful) { return new ParseResult( diff --git a/packages/combinator/lib/src/combinator/opt.dart b/packages/combinator/lib/src/combinator/opt.dart index 3f31c995..ac2b31c2 100644 --- a/packages/combinator/lib/src/combinator/opt.dart +++ b/packages/combinator/lib/src/combinator/opt.dart @@ -9,7 +9,7 @@ class _Opt extends Parser { @override ParseResult __parse(ParseArgs args) { var replay = args.scanner.position; - var result = parser._parse(args.increaseDepth()); + var result = parser._parse(args.increaseDepth())!; if (!result.successful) args.scanner.position = replay; @@ -37,7 +37,7 @@ class _ListOpt extends ListParser { @override ParseResult> __parse(ParseArgs args) { var replay = args.scanner.position; - var result = parser._parse(args.increaseDepth()); + ParseResult> result = parser._parse(args.increaseDepth())!; if (!result.successful) args.scanner.position = replay; diff --git a/packages/combinator/lib/src/combinator/reduce.dart b/packages/combinator/lib/src/combinator/reduce.dart index cb88f94b..ae2d7d14 100644 --- a/packages/combinator/lib/src/combinator/reduce.dart +++ b/packages/combinator/lib/src/combinator/reduce.dart @@ -8,7 +8,7 @@ class _Reduce extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth()); + ParseResult> result = parser._parse(args.increaseDepth())!; if (!result.successful) return new ParseResult( @@ -28,7 +28,7 @@ class _Reduce extends Parser { result.successful, [], span: result.span, - value: result.value.isEmpty ? null : result.value.reduce(combine), + value: result.value!.isEmpty ? null : result.value!.reduce(combine), ); } diff --git a/packages/combinator/lib/src/combinator/reference.dart b/packages/combinator/lib/src/combinator/reference.dart index d46f5a6e..dd2b6b9b 100644 --- a/packages/combinator/lib/src/combinator/reference.dart +++ b/packages/combinator/lib/src/combinator/reference.dart @@ -3,7 +3,7 @@ part of lex.src.combinator; Reference reference() => new Reference._(); class Reference extends Parser { - Parser _parser; + Parser? _parser; bool printed = false; Reference._(); @@ -16,22 +16,22 @@ class Reference extends Parser { } @override - ParseResult __parse(ParseArgs args) { + ParseResult? __parse(ParseArgs args) { if (_parser == null) throw new StateError('There is no parser assigned to this reference.'); - return _parser._parse(args); + return _parser!._parse(args); } @override - ParseResult _parse(ParseArgs args) { - return _parser._parse(args); + ParseResult? _parse(ParseArgs args) { + return _parser!._parse(args); } @override void stringify(CodeBuffer buffer) { if (_parser == null) buffer.writeln('(undefined reference <$T>)'); - else if (!printed) _parser.stringify(buffer); + else if (!printed) _parser!.stringify(buffer); printed = true; buffer.writeln('(previously printed reference)'); } diff --git a/packages/combinator/lib/src/combinator/repeat.dart b/packages/combinator/lib/src/combinator/repeat.dart index 4f62b5e4..be8b9c57 100644 --- a/packages/combinator/lib/src/combinator/repeat.dart +++ b/packages/combinator/lib/src/combinator/repeat.dart @@ -1,26 +1,26 @@ part of lex.src.combinator; -class _Repeat extends ListParser { +class _Repeat extends ListParser { final Parser parser; final int count; final bool exact, backtrack; - final String tooFew, tooMany; + final String? tooFew, tooMany; final SyntaxErrorSeverity severity; _Repeat(this.parser, this.count, this.exact, this.tooFew, this.tooMany, this.backtrack, this.severity); @override - ParseResult> __parse(ParseArgs args) { + ParseResult> __parse(ParseArgs args) { var errors = []; - var results = []; - var spans = []; + var results = []; + var spans = []; int success = 0, replay = args.scanner.position; - ParseResult result; + ParseResult? result; do { result = parser._parse(args.increaseDepth()); - if (result.successful) { + if (result!.successful) { success++; results.add(result.value); replay = args.scanner.position; @@ -41,12 +41,12 @@ class _Repeat extends ListParser { if (backtrack) args.scanner.position = replay; - return new ParseResult>( + return new ParseResult>( args.trampoline, args.scanner, this, false, errors); } else if (success > count && exact) { if (backtrack) args.scanner.position = replay; - return new ParseResult>( + return new ParseResult>( args.trampoline, args.scanner, this, false, [ new SyntaxError( severity, @@ -56,8 +56,8 @@ class _Repeat extends ListParser { ]); } - var span = spans.reduce((a, b) => a.expand(b)); - return new ParseResult>( + var span = spans.reduce((a, b) => a!.expand(b!)); + return new ParseResult>( args.trampoline, args.scanner, this, diff --git a/packages/combinator/lib/src/combinator/safe.dart b/packages/combinator/lib/src/combinator/safe.dart index 02127275..8bac100e 100644 --- a/packages/combinator/lib/src/combinator/safe.dart +++ b/packages/combinator/lib/src/combinator/safe.dart @@ -3,18 +3,18 @@ part of lex.src.combinator; class _Safe extends Parser { final Parser parser; final bool backtrack; - final String errorMessage; + final String? errorMessage; final SyntaxErrorSeverity severity; bool _triggered = false; _Safe(this.parser, this.backtrack, this.errorMessage, this.severity); @override - ParseResult __parse(ParseArgs args) { + ParseResult? __parse(ParseArgs args) { var replay = args.scanner.position; try { - if (_triggered) throw null; + if (_triggered) throw Exception(); return parser._parse(args.increaseDepth()); } catch (_) { _triggered = true; diff --git a/packages/combinator/lib/src/combinator/to_list.dart b/packages/combinator/lib/src/combinator/to_list.dart index eb54b5c4..a339db50 100644 --- a/packages/combinator/lib/src/combinator/to_list.dart +++ b/packages/combinator/lib/src/combinator/to_list.dart @@ -1,16 +1,16 @@ part of lex.src.combinator; -class _ToList extends ListParser { +class _ToList extends ListParser { final Parser parser; _ToList(this.parser); @override - ParseResult> __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth()); + ParseResult> __parse(ParseArgs args) { + var result = parser._parse(args.increaseDepth())!; if (result.value is List) { - return (result as ParseResult>).change(parser: this); + return (result as ParseResult>).change(parser: this); } return new ParseResult( diff --git a/packages/combinator/lib/src/combinator/util.dart b/packages/combinator/lib/src/combinator/util.dart index 7e0b56c4..e10d7f5f 100644 --- a/packages/combinator/lib/src/combinator/util.dart +++ b/packages/combinator/lib/src/combinator/util.dart @@ -1,48 +1,48 @@ part of lex.src.combinator; /// A typed parser that parses a sequence of 2 values of different types. -Parser> tuple2(Parser a, Parser b) { +Parser> tuple2(Parser a, Parser b) { return chain([a, b]).map((r) { - return Tuple2(r.value[0] as A, r.value[1] as B); + return Tuple2(r.value![0] as A?, r.value![1] as B?); }); } /// A typed parser that parses a sequence of 3 values of different types. -Parser> tuple3(Parser a, Parser b, Parser c) { +Parser> tuple3(Parser a, Parser b, Parser c) { return chain([a, b, c]).map((r) { - return Tuple3(r.value[0] as A, r.value[1] as B, r.value[2] as C); + return Tuple3(r.value![0] as A?, r.value![1] as B?, r.value![2] as C?); }); } /// A typed parser that parses a sequence of 4 values of different types. -Parser> tuple4( +Parser> tuple4( Parser a, Parser b, Parser c, Parser d) { return chain([a, b, c, d]).map((r) { return Tuple4( - r.value[0] as A, r.value[1] as B, r.value[2] as C, r.value[3] as D); + r.value![0] as A?, r.value![1] as B?, r.value![2] as C?, r.value![3] as D?); }); } /// A typed parser that parses a sequence of 5 values of different types. -Parser> tuple5( +Parser> tuple5( Parser a, Parser b, Parser c, Parser d, Parser e) { return chain([a, b, c, d, e]).map((r) { - return Tuple5(r.value[0] as A, r.value[1] as B, r.value[2] as C, - r.value[3] as D, r.value[4] as E); + return Tuple5(r.value![0] as A?, r.value![1] as B?, r.value![2] as C?, + r.value![3] as D?, r.value![4] as E?); }); } /// A typed parser that parses a sequence of 6 values of different types. -Parser> tuple6(Parser a, +Parser> tuple6(Parser a, Parser b, Parser c, Parser d, Parser e, Parser f) { return chain([a, b, c, d, e, f]).map((r) { - return Tuple6(r.value[0] as A, r.value[1] as B, r.value[2] as C, - r.value[3] as D, r.value[4] as E, r.value[5] as F); + return Tuple6(r.value![0] as A?, r.value![1] as B?, r.value![2] as C?, + r.value![3] as D?, r.value![4] as E?, r.value![5] as F?); }); } /// A typed parser that parses a sequence of 7 values of different types. -Parser> tuple7( +Parser> tuple7( Parser a, Parser b, Parser c, @@ -51,7 +51,7 @@ Parser> tuple7( Parser f, Parser g) { return chain([a, b, c, d, e, f, g]).map((r) { - return Tuple7(r.value[0] as A, r.value[1] as B, r.value[2] as C, - r.value[3] as D, r.value[4] as E, r.value[5] as F, r.value[6] as G); + return Tuple7(r.value![0] as A?, r.value![1] as B?, r.value![2] as C?, + r.value![3] as D?, r.value![4] as E?, r.value![5] as F?, r.value![6] as G?); }); } diff --git a/packages/combinator/lib/src/combinator/value.dart b/packages/combinator/lib/src/combinator/value.dart index b0612073..ab206187 100644 --- a/packages/combinator/lib/src/combinator/value.dart +++ b/packages/combinator/lib/src/combinator/value.dart @@ -8,7 +8,7 @@ class _Value extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth()).change(parser: this); + var result = parser._parse(args.increaseDepth())!.change(parser: this); return result.successful ? result.change(value: f(result)) : result; } diff --git a/packages/combinator/lib/src/error.dart b/packages/combinator/lib/src/error.dart index e6b4de09..6c7b5f51 100644 --- a/packages/combinator/lib/src/error.dart +++ b/packages/combinator/lib/src/error.dart @@ -2,16 +2,16 @@ import 'package:source_span/source_span.dart'; class SyntaxError implements Exception { final SyntaxErrorSeverity severity; - final String message; - final FileSpan span; - String _toolString; + final String? message; + final FileSpan? span; + String? _toolString; SyntaxError(this.severity, this.message, this.span); - String get toolString { + String? get toolString { if (_toolString != null) return _toolString; var type = severity == SyntaxErrorSeverity.warning ? 'warning' : 'error'; - return _toolString = '$type: ${span.start.toolString}: $message'; + return _toolString = '$type: ${span!.start.toolString}: $message'; } } diff --git a/packages/combinator/pubspec.yaml b/packages/combinator/pubspec.yaml index 122c162c..c0894fdb 100644 --- a/packages/combinator/pubspec.yaml +++ b/packages/combinator/pubspec.yaml @@ -5,7 +5,7 @@ author: Tobe O homepage: https://github.com/thosakwe/combinator.git publish_to: none environment: - sdk: ">=2.10.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: code_buffer: git: diff --git a/packages/combinator/test/list_test.dart b/packages/combinator/test/list_test.dart index 06e7dd4b..64bf8fba 100644 --- a/packages/combinator/test/list_test.dart +++ b/packages/combinator/test/list_test.dart @@ -4,19 +4,19 @@ import 'common.dart'; main() { var number = chain([ - match(new RegExp(r'[0-9]+')).value((r) => int.parse(r.span.text)), + match(new RegExp(r'[0-9]+')).value((r) => int.parse(r.span!.text)), match(',').opt(), ]).first().cast(); var numbers = number.plus(); test('sort', () { - var parser = numbers.sort((a, b) => a.compareTo(b)); - expect(parser.parse(scan('21,2,3,34,20')).value, [2, 3, 20, 21, 34]); + var parser = numbers.sort((a, b) => a!.compareTo(b!)); + expect(parser.parse(scan('21,2,3,34,20'))!.value, [2, 3, 20, 21, 34]); }); test('reduce', () { - var parser = numbers.reduce((a, b) => a + b); - expect(parser.parse(scan('21,2,3,34,20')).value, 80); - expect(parser.parse(scan('not numbers')).value, isNull); + var parser = numbers.reduce((a, b) => a! + b!); + expect(parser.parse(scan('21,2,3,34,20'))!.value, 80); + expect(parser.parse(scan('not numbers'))!.value, isNull); }); } diff --git a/packages/combinator/test/match_test.dart b/packages/combinator/test/match_test.dart index 60504d93..d1406276 100644 --- a/packages/combinator/test/match_test.dart +++ b/packages/combinator/test/match_test.dart @@ -4,13 +4,13 @@ import 'common.dart'; main() { test('match string', () { - expect(match('hello').parse(scan('hello world')).successful, isTrue); + expect(match('hello').parse(scan('hello world'))!.successful, isTrue); }); test('match start only', () { - expect(match('hello').parse(scan('goodbye hello')).successful, isFalse); + expect(match('hello').parse(scan('goodbye hello'))!.successful, isFalse); }); test('fail if no match', () { - expect(match('hello').parse(scan('world')).successful, isFalse); + expect(match('hello').parse(scan('world'))!.successful, isFalse); }); } diff --git a/packages/combinator/test/misc_test.dart b/packages/combinator/test/misc_test.dart index aca09d50..f90d5d83 100644 --- a/packages/combinator/test/misc_test.dart +++ b/packages/combinator/test/misc_test.dart @@ -14,28 +14,29 @@ main() { }); test('change', () { - var parser = match('hello').change((r) => r.change(value: 23)); - expect(parser.parse(scan('helloworld')).value, 23); + var parser = match('hello').change((r) => r!.change(value: 23)); + expect(parser.parse(scan('helloworld'))!.value, 23); }); test('check', () { var parser = match(new RegExp(r'[A-Za-z]+')) - .value((r) => r.span.length) + .value((r) => r.span!.length) .check(greaterThan(3)); - expect(parser.parse(scan('helloworld')).successful, isTrue); - expect(parser.parse(scan('yo')).successful, isFalse); + expect(parser.parse(scan('helloworld'))!.successful, isTrue); + expect(parser.parse(scan('yo'))!.successful, isFalse); }); test('map', () { - var parser = match(new RegExp(r'[A-Za-z]+')).map((r) => r.span.length); - expect(parser.parse(scan('hello')).value, 5); + var parser = + match(new RegExp(r'[A-Za-z]+')).map((r) => r.span!.length); + expect(parser.parse(scan('hello'))!.value, 5); }); test('negate', () { var parser = match('hello').negate(errorMessage: 'world'); - expect(parser.parse(scan('goodbye world')).successful, isTrue); - expect(parser.parse(scan('hello world')).successful, isFalse); - expect(parser.parse(scan('hello world')).errors.first.message, 'world'); + expect(parser.parse(scan('goodbye world'))!.successful, isTrue); + expect(parser.parse(scan('hello world'))!.successful, isFalse); + expect(parser.parse(scan('hello world'))!.errors.first.message, 'world'); }); group('opt', () { @@ -43,13 +44,13 @@ main() { var list = match('hel').then(match('lo')).opt(); test('succeeds if present', () { - expect(single.parse(scan('hello')).successful, isTrue); - expect(list.parse(scan('hello')).successful, isTrue); + expect(single.parse(scan('hello'))!.successful, isTrue); + expect(list.parse(scan('hello'))!.successful, isTrue); }); test('succeeds if not present', () { - expect(single.parse(scan('goodbye')).successful, isTrue); - expect(list.parse(scan('goodbye')).successful, isTrue); + expect(single.parse(scan('goodbye'))!.successful, isTrue); + expect(list.parse(scan('goodbye'))!.successful, isTrue); }); test('backtracks if not present', () { diff --git a/packages/combinator/test/value_test.dart b/packages/combinator/test/value_test.dart index c987d10f..97ab3543 100644 --- a/packages/combinator/test/value_test.dart +++ b/packages/combinator/test/value_test.dart @@ -6,10 +6,10 @@ main() { var parser = match('hello').value((r) => 'world'); test('sets value', () { - expect(parser.parse(scan('hello world')).value, 'world'); + expect(parser.parse(scan('hello world'))!.value, 'world'); }); test('no value if no match', () { - expect(parser.parse(scan('goodbye world')).value, isNull); + expect(parser.parse(scan('goodbye world'))!.value, isNull); }); } From 5d722a604121641b19e5513a2ff07c570ab9c55e Mon Sep 17 00:00:00 2001 From: thomashii Date: Thu, 18 Mar 2021 08:11:45 +0800 Subject: [PATCH 006/171] Updated Angel Route to Null Safety --- packages/route/.idea/misc.xml | 6 -- packages/route/.idea/modules.xml | 8 -- .../runConfigurations/tests_in_route.xml | 8 -- packages/route/example/main.dart | 2 +- packages/route/lib/browser.dart | 48 ++++++------ packages/route/lib/src/grammar.dart | 70 +++++++++--------- .../route/lib/src/middleware_pipeline.dart | 4 +- packages/route/lib/src/route.dart | 32 ++++---- packages/route/lib/src/router.dart | 74 +++++++++---------- packages/route/lib/src/routing_result.dart | 34 ++++----- packages/route/pubspec.yaml | 13 ++-- packages/route/test/chain_nest_test.dart | 2 +- packages/route/test/parse_test.dart | 4 +- packages/route/test/server_test.dart | 46 ++++++------ packages/route/test/wildcard_test.dart | 6 +- packages/route/web/shared/basic.dart | 10 +-- 16 files changed, 174 insertions(+), 193 deletions(-) delete mode 100644 packages/route/.idea/misc.xml delete mode 100644 packages/route/.idea/modules.xml delete mode 100644 packages/route/.idea/runConfigurations/tests_in_route.xml diff --git a/packages/route/.idea/misc.xml b/packages/route/.idea/misc.xml deleted file mode 100644 index 639900d1..00000000 --- a/packages/route/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/packages/route/.idea/modules.xml b/packages/route/.idea/modules.xml deleted file mode 100644 index b6a601b4..00000000 --- a/packages/route/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/packages/route/.idea/runConfigurations/tests_in_route.xml b/packages/route/.idea/runConfigurations/tests_in_route.xml deleted file mode 100644 index 9f2a66b3..00000000 --- a/packages/route/.idea/runConfigurations/tests_in_route.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/packages/route/example/main.dart b/packages/route/example/main.dart index 7390c125..b0352ac2 100644 --- a/packages/route/example/main.dart +++ b/packages/route/example/main.dart @@ -12,7 +12,7 @@ main() { router.get('/ordinal/int:n([0-9]+)st', () {}); print(router.resolveAbsolute('/whois/~thosakwe').first.allParams); - print(router.resolveAbsolute('/wild_thornberrys').first.route.path); + print(router.resolveAbsolute('/wild_thornberrys').first.route!.path); print(router.resolveAbsolute('/ordinal/1st').first.allParams); router.get('/users', () {}); diff --git a/packages/route/lib/browser.dart b/packages/route/lib/browser.dart index 81969d66..eb54fe45 100644 --- a/packages/route/lib/browser.dart +++ b/packages/route/lib/browser.dart @@ -11,10 +11,10 @@ final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)'); /// A variation of the [Router] support both hash routing and push state. abstract class BrowserRouter extends Router { /// Fires whenever the active route changes. Fires `null` if none is selected (404). - Stream> get onResolve; + Stream?> get onResolve; /// Fires whenever the active route changes. Fires `null` if none is selected (404). - Stream> get onRoute; + Stream?> get onRoute; /// Set `hash` to true to use hash routing instead of push state. /// `listen` as `true` will call `listen` after initialization. @@ -26,7 +26,7 @@ abstract class BrowserRouter extends Router { BrowserRouter._() : super(); - void _goTo(String path); + void _goTo(String? path); /// Navigates to the path generated by calling /// [navigate] with the given [linkParams]. @@ -41,26 +41,26 @@ abstract class BrowserRouter extends Router { void listen(); /// Identical to [all]. - Route on(String path, T handler, {Iterable middleware}); + Route on(String path, T handler, {Iterable? middleware}); } abstract class _BrowserRouterImpl extends Router implements BrowserRouter { bool _listening = false; - Route _current; - StreamController> _onResolve = - StreamController>(); - StreamController> _onRoute = StreamController>(); + Route? _current; + StreamController?> _onResolve = + StreamController?>(); + StreamController?> _onRoute = StreamController?>(); - Route get currentRoute => _current; + Route? get currentRoute => _current; @override - Stream> get onResolve => _onResolve.stream; + Stream?> get onResolve => _onResolve.stream; @override - Stream> get onRoute => _onRoute.stream; + Stream?> get onRoute => _onRoute.stream; - _BrowserRouterImpl({bool listen}) : super() { + _BrowserRouterImpl({bool? listen}) : super() { if (listen != false) this.listen(); prepareAnchors(); } @@ -68,7 +68,7 @@ abstract class _BrowserRouterImpl extends Router @override void go(Iterable linkParams) => _goTo(navigate(linkParams)); - Route on(String path, T handler, {Iterable middleware}) => + Route on(String path, T handler, {Iterable? middleware}) => all(path, handler, middleware: middleware); void prepareAnchors() { @@ -105,18 +105,18 @@ abstract class _BrowserRouterImpl extends Router } class _HashRouter extends _BrowserRouterImpl { - _HashRouter({bool listen}) : super(listen: listen) { + _HashRouter({required bool listen}) : super(listen: listen) { if (listen) this.listen(); } @override - void _goTo(String uri) { + void _goTo(String? uri) { window.location.hash = '#$uri'; } void handleHash([_]) { final path = window.location.hash.replaceAll(_hash, ''); - var allResolved = resolveAbsolute(path); + Iterable> allResolved = resolveAbsolute(path); final resolved = allResolved.isEmpty ? null : allResolved.first; @@ -130,7 +130,7 @@ class _HashRouter extends _BrowserRouterImpl { } void handlePath(String path) { - final resolved = resolveAbsolute(path).first; + final RoutingResult resolved = resolveAbsolute(path).first; if (resolved == null) { _onResolve.add(null); @@ -149,9 +149,9 @@ class _HashRouter extends _BrowserRouterImpl { } class _PushStateRouter extends _BrowserRouterImpl { - String _basePath; + String? _basePath; - _PushStateRouter({bool listen, Route root}) : super(listen: listen) { + _PushStateRouter({required bool listen, Route? root}) : super(listen: listen) { var $base = window.document.querySelector('base[href]') as BaseElement; if ($base?.href?.isNotEmpty != true) { @@ -163,19 +163,19 @@ class _PushStateRouter extends _BrowserRouterImpl { } @override - void _goTo(String uri) { - final resolved = resolveAbsolute(uri).first; + void _goTo(String? uri) { + final RoutingResult resolved = resolveAbsolute(uri).first; var relativeUri = uri; if (_basePath?.isNotEmpty == true) { - relativeUri = p.join(_basePath, uri.replaceAll(_straySlashes, '')); + relativeUri = p.join(_basePath!, uri!.replaceAll(_straySlashes, '')); } if (resolved == null) { _onResolve.add(null); _onRoute.add(_current = null); } else { - final route = resolved.route; + final route = resolved.route!; window.history.pushState({'path': route.path, 'params': {}}, route.name ?? route.path, relativeUri); _onResolve.add(resolved); @@ -186,7 +186,7 @@ class _PushStateRouter extends _BrowserRouterImpl { void handleState(state) { if (state is Map && state.containsKey('path')) { var path = state['path'].toString(); - final resolved = resolveAbsolute(path).first; + final RoutingResult resolved = resolveAbsolute(path).first; if (resolved != null && resolved.route != _current) { //properties.addAll(state['properties'] ?? {}); diff --git a/packages/route/lib/src/grammar.dart b/packages/route/lib/src/grammar.dart index dbb36a74..e2974c15 100644 --- a/packages/route/lib/src/grammar.dart +++ b/packages/route/lib/src/grammar.dart @@ -4,14 +4,14 @@ class RouteGrammar { static const String notSlashRgx = r'([^/]+)'; //static final RegExp rgx = RegExp(r'\((.+)\)'); static final Parser notSlash = - match(RegExp(notSlashRgx)).value((r) => r.span.text); + match(RegExp(notSlashRgx)).value((r) => r.span!.text); - static final Parser regExp = - match(RegExp(r'\(([^\n)]+)\)([^/]+)?')) + static final Parser regExp = + match(RegExp(r'\(([^\n)]+)\)([^/]+)?')) .value((r) => r.scanner.lastMatch); - static final Parser parameterName = - match(RegExp('$notSlashRgx?' r':([A-Za-z0-9_]+)' r'([^(/\n])?')) + static final Parser parameterName = + match(RegExp('$notSlashRgx?' r':([A-Za-z0-9_]+)' r'([^(/\n])?')) .value((r) => r.scanner.lastMatch); static final Parser parameterSegment = chain([ @@ -19,12 +19,12 @@ class RouteGrammar { match('?').value((r) => true).opt(), regExp.opt(), ]).map((r) { - var match = r.value[0] as Match; - var rgxMatch = r.value[2] as Match; + var match = r.value![0] as Match; + var rgxMatch = r.value![2] as Match?; var pre = match[1] ?? ''; var post = match[3] ?? ''; - RegExp rgx; + RegExp? rgx; if (rgxMatch != null) { rgx = RegExp('(${rgxMatch[1]})'); @@ -41,23 +41,23 @@ class RouteGrammar { } var s = ParameterSegment(match[2], rgx); - return r.value[1] == true ? OptionalSegment(s) : s; + return r.value![1] == true ? OptionalSegment(s) : s; }); static final Parser parsedParameterSegment = chain([ match(RegExp(r'(int|num|double)'), errorMessage: 'Expected "int","double", or "num".') - .map((r) => r.span.text), + .map((r) => r.span!.text), parameterSegment, ]).map((r) { return ParsedParameterSegment( - r.value[0] as String, r.value[1] as ParameterSegment); + r.value![0] as String, r.value![1] as ParameterSegment); }); static final Parser wildcardSegment = match(RegExp('$notSlashRgx?' r'\*' '$notSlashRgx?')) .value((r) { - var m = r.scanner.lastMatch; + var m = r.scanner.lastMatch!; var pre = m[1] ?? ''; var post = m[2] ?? ''; return WildcardSegment(pre, post); @@ -95,8 +95,8 @@ class RouteDefinition { RouteDefinition(this.segments); - Parser compile() { - Parser out; + Parser? compile() { + Parser? out; for (int i = 0; i < segments.length; i++) { var s = segments[i]; @@ -116,7 +116,7 @@ class RouteDefinition { abstract class RouteSegment { Parser compile(bool isLast); - Parser compileNext(Parser p, bool isLast); + Parser compileNext(Parser p, bool isLast); } class SlashSegment implements RouteSegment { @@ -139,7 +139,7 @@ class SlashSegment implements RouteSegment { } class ConstantSegment extends RouteSegment { - final String text; + final String? text; ConstantSegment(this.text); @@ -150,7 +150,7 @@ class ConstantSegment extends RouteSegment { @override Parser compile(bool isLast) { - return match(text).map((r) => RouteResult({})); + return match(text!).map((r) => RouteResult({})); } @override @@ -183,13 +183,13 @@ class WildcardSegment extends RouteSegment { @override Parser compile(bool isLast) { return match(_compile(isLast)) - .map((r) => RouteResult({}, tail: r.scanner.lastMatch[1])); + .map((r) => RouteResult({}, tail: r.scanner.lastMatch![1])); } @override Parser compileNext(Parser p, bool isLast) { return p.then(compile(isLast)).map((r) { - var items = r.value.cast(); + var items = r.value!.cast(); var a = items[0], b = items[1]; return a ..addAll(b?.params ?? {}) @@ -214,44 +214,44 @@ class OptionalSegment extends ParameterSegment { } @override - Parser compileNext(Parser p, bool isLast) { + Parser compileNext(Parser p, bool isLast) { return p.then(_compile().opt()).map((r) { - if (r.value[1] == null) return r.value[0] as RouteResult; - return (r.value[0] as RouteResult) - ..addAll({name: Uri.decodeComponent(r.value[1] as String)}); + if (r.value![1] == null) return r.value![0] as RouteResult?; + return (r.value![0] as RouteResult) + ..addAll({name: Uri.decodeComponent(r.value![1] as String)}); }); } } class ParameterSegment extends RouteSegment { - final String name; - final RegExp regExp; + final String? name; + final RegExp? regExp; ParameterSegment(this.name, this.regExp); @override String toString() { - if (regExp != null) return 'Param: $name (${regExp.pattern})'; + if (regExp != null) return 'Param: $name (${regExp!.pattern})'; return 'Param: $name'; } - Parser _compile() { + Parser _compile() { return regExp != null - ? match(regExp).value((r) => r.scanner.lastMatch[1]) + ? match(regExp!).value((r) => r.scanner.lastMatch![1]) : RouteGrammar.notSlash; } @override Parser compile(bool isLast) { return _compile() - .map((r) => RouteResult({name: Uri.decodeComponent(r.value)})); + .map((r) => RouteResult({name: Uri.decodeComponent(r.value!)})); } @override - Parser compileNext(Parser p, bool isLast) { + Parser compileNext(Parser p, bool isLast) { return p.then(_compile()).map((r) { - return (r.value[0] as RouteResult) - ..addAll({name: Uri.decodeComponent(r.value[1] as String)}); + return (r.value![0] as RouteResult) + ..addAll({name: Uri.decodeComponent(r.value![1] as String)}); }); } } @@ -276,15 +276,15 @@ class ParsedParameterSegment extends RouteSegment { @override Parser compile(bool isLast) { return parameter._compile().map((r) => RouteResult( - {parameter.name: getValue(Uri.decodeComponent(r.span.text))})); + {parameter.name: getValue(Uri.decodeComponent(r.span!.text))})); } @override Parser compileNext(Parser p, bool isLast) { return p.then(parameter._compile()).map((r) { - return (r.value[0] as RouteResult) + return (r.value![0] as RouteResult) ..addAll({ - parameter.name: getValue(Uri.decodeComponent(r.value[1] as String)) + parameter.name: getValue(Uri.decodeComponent(r.value![1] as String)) }); }); } diff --git a/packages/route/lib/src/middleware_pipeline.dart b/packages/route/lib/src/middleware_pipeline.dart index afb1b9e4..cf44f78c 100644 --- a/packages/route/lib/src/middleware_pipeline.dart +++ b/packages/route/lib/src/middleware_pipeline.dart @@ -4,10 +4,10 @@ import 'router.dart'; class MiddlewarePipeline { /// All the possible routes that matched the given path. final Iterable> routingResults; - List _handlers; + List? _handlers; /// An ordered list of every handler delegated to handle this request. - List get handlers { + List? get handlers { if (_handlers != null) return _handlers; final handlers = []; diff --git a/packages/route/lib/src/route.dart b/packages/route/lib/src/route.dart index b20b50c9..2989e36a 100644 --- a/packages/route/lib/src/route.dart +++ b/packages/route/lib/src/route.dart @@ -2,17 +2,17 @@ part of angel_route.src.router; /// Represents a virtual location within an application. class Route { - final String method; + final String? method; final String path; - final List handlers; + final List? handlers; final Map> _cache = {}; - final RouteDefinition _routeDefinition; - String name; - Parser _parser; + final RouteDefinition? _routeDefinition; + String? name; + Parser? _parser; - Route(this.path, {@required this.method, @required this.handlers}) + Route(this.path, {required this.method, required this.handlers}) : _routeDefinition = RouteGrammar.routeDefinition - .parse(SpanScanner(path.replaceAll(_straySlashes, ''))) + .parse(SpanScanner(path.replaceAll(_straySlashes, '')))! .value { if (_routeDefinition?.segments?.isNotEmpty != true) { _parser = match('').map((r) => RouteResult({})); @@ -26,7 +26,7 @@ class Route { method: b.method, handlers: b.handlers); } - Parser get parser => _parser ??= _routeDefinition.compile(); + Parser? get parser => _parser ??= _routeDefinition!.compile(); @override String toString() { @@ -42,7 +42,7 @@ class Route { var b = StringBuffer(); int i = 0; - for (var seg in _routeDefinition.segments) { + for (var seg in _routeDefinition!.segments) { if (i++ > 0) b.write('/'); if (seg is ConstantSegment) { b.write(seg.text); @@ -50,7 +50,7 @@ class Route { if (!params.containsKey(seg.name)) { throw ArgumentError('Missing parameter "${seg.name}".'); } - b.write(params[seg.name]); + b.write(params[seg.name!]); } } @@ -61,19 +61,19 @@ class Route { /// The result of matching an individual route. class RouteResult { /// The parsed route parameters. - final Map params; + final Map params; /// Optional. An explicit "tail" value to set. - String get tail => _tail; + String? get tail => _tail; - String _tail; + String? _tail; - RouteResult(this.params, {String tail}) : _tail = tail; + RouteResult(this.params, {String? tail}) : _tail = tail; - void _setTail(String v) => _tail ??= v; + void _setTail(String? v) => _tail ??= v; /// Adds parameters. - void addAll(Map map) { + void addAll(Map map) { params.addAll(map); } } diff --git a/packages/route/lib/src/router.dart b/packages/route/lib/src/router.dart index 9d6e4f1c..986844d4 100644 --- a/packages/route/lib/src/router.dart +++ b/packages/route/lib/src/router.dart @@ -65,8 +65,8 @@ class Router { /// Adds a route that responds to the given path /// for requests with the given method (case-insensitive). /// Provide '*' as the method to respond to all methods. - Route addRoute(String method, String path, T handler, - {Iterable middleware}) { + Route addRoute(String? method, String path, T handler, + {Iterable? middleware}) { middleware ??= []; if (_useCache == true) { throw StateError('Cannot add routes after caching is enabled.'); @@ -115,7 +115,7 @@ class Router { /// Creates a visual representation of the route hierarchy and /// passes it to a callback. If none is provided, `print` is called. void dumpTree( - {callback(String tree), + {callback(String tree)?, String header = 'Dumping route tree:', String tab = ' '}) { final buf = StringBuffer(); @@ -147,7 +147,7 @@ class Router { buf.writeln(); dumpRouter(route.router); } else { - buf.writeln(' => ${route.handlers.length} handler(s)'); + buf.writeln(' => ${route.handlers!.length} handler(s)'); } } @@ -165,7 +165,7 @@ class Router { /// Returns the created route. /// You can also register middleware within the router. SymlinkRoute group(String path, void callback(Router router), - {Iterable middleware, String name}) { + {Iterable? middleware, String? name}) { middleware ??= []; final router = Router().._middleware.addAll(middleware); callback(router); @@ -175,7 +175,7 @@ class Router { /// Asynchronous equivalent of [group]. Future> groupAsync( String path, FutureOr callback(Router router), - {Iterable middleware, String name}) async { + {Iterable? middleware, String? name}) async { middleware ??= []; final router = Router().._middleware.addAll(middleware); await callback(router); @@ -212,7 +212,7 @@ class Router { String navigate(Iterable linkParams, {bool absolute = true}) { final List segments = []; Router search = this; - Route lastRoute; + Route? lastRoute; for (final param in linkParams) { bool resolved = false; @@ -238,7 +238,7 @@ class Router { var scanner = SpanScanner(param.replaceAll(_straySlashes, '')); for (Route route in search.routes) { int pos = scanner.position; - if (route.parser.parse(scanner).successful && scanner.isDone) { + if (route.parser!.parse(scanner)!.successful && scanner.isDone) { segments.add(route.path.replaceAll(_straySlashes, '')); lastRoute = route; @@ -281,10 +281,10 @@ class Router { /// Finds the first [Route] that matches the given path, /// with the given method. - bool resolve(String absolute, String relative, List> out, + bool resolve(String? absolute, String? relative, List> out, {String method = 'GET', bool strip = true}) { final cleanRelative = - strip == false ? relative : stripStraySlashes(relative); + strip == false ? relative! : stripStraySlashes(relative!); var scanner = SpanScanner(cleanRelative); bool crawl(Router r) { @@ -294,22 +294,22 @@ class Router { int pos = scanner.position; if (route is SymlinkRoute) { - if (route.parser.parse(scanner).successful) { + if (route.parser!.parse(scanner)!.successful) { var s = crawl(route.router); if (s) success = true; } scanner.position = pos; } else if (route.method == '*' || route.method == method) { - var parseResult = route.parser.parse(scanner); + var parseResult = route.parser!.parse(scanner)!; if (parseResult.successful && scanner.isDone) { var result = RoutingResult( parseResult: parseResult, - params: parseResult.value.params, + params: parseResult.value!.params, shallowRoute: route, shallowRouter: this, - tail: (parseResult.value.tail ?? '') + scanner.rest); + tail: (parseResult.value!.tail ?? '') + scanner.rest); out.add(result); success = true; } @@ -326,13 +326,13 @@ class Router { /// Returns the result of [resolve] with [path] passed as /// both `absolute` and `relative`. - Iterable> resolveAbsolute(String path, + Iterable> resolveAbsolute(String? path, {String method = 'GET', bool strip = true}) => resolveAll(path, path, method: method, strip: strip); /// Finds every possible [Route] that matches the given path, /// with the given method. - Iterable> resolveAll(String absolute, String relative, + Iterable> resolveAll(String? absolute, String? relative, {String method = 'GET', bool strip = true}) { if (_useCache == true) { return _cache.putIfAbsent('$method$absolute', @@ -342,7 +342,7 @@ class Router { return _resolveAll(absolute, relative, method: method, strip: strip); } - Iterable> _resolveAll(String absolute, String relative, + Iterable> _resolveAll(String? absolute, String? relative, {String method = 'GET', bool strip = true}) { var results = >[]; resolve(absolute, relative, results, method: method, strip: strip); @@ -363,61 +363,61 @@ class Router { } /// Adds a route that responds to any request matching the given path. - Route all(String path, T handler, {Iterable middleware}) { + Route all(String path, T handler, {Iterable? middleware}) { return addRoute('*', path, handler, middleware: middleware); } /// Adds a route that responds to a DELETE request. - Route delete(String path, T handler, {Iterable middleware}) { + Route delete(String path, T handler, {Iterable? middleware}) { return addRoute('DELETE', path, handler, middleware: middleware); } /// Adds a route that responds to a GET request. - Route get(String path, T handler, {Iterable middleware}) { + Route get(String path, T handler, {Iterable? middleware}) { return addRoute('GET', path, handler, middleware: middleware); } /// Adds a route that responds to a HEAD request. - Route head(String path, T handler, {Iterable middleware}) { + Route head(String path, T handler, {Iterable? middleware}) { return addRoute('HEAD', path, handler, middleware: middleware); } /// Adds a route that responds to a OPTIONS request. - Route options(String path, T handler, {Iterable middleware}) { + Route options(String path, T handler, {Iterable? middleware}) { return addRoute('OPTIONS', path, handler, middleware: middleware); } /// Adds a route that responds to a POST request. - Route post(String path, T handler, {Iterable middleware}) { + Route post(String path, T handler, {Iterable? middleware}) { return addRoute('POST', path, handler, middleware: middleware); } /// Adds a route that responds to a PATCH request. - Route patch(String path, T handler, {Iterable middleware}) { + Route patch(String path, T handler, {Iterable? middleware}) { return addRoute('PATCH', path, handler, middleware: middleware); } /// Adds a route that responds to a PUT request. - Route put(String path, T handler, {Iterable middleware}) { + Route put(String path, T handler, {Iterable? middleware}) { return addRoute('PUT', path, handler, middleware: middleware); } } class _ChainedRouter extends Router { final List _handlers = []; - Router _root; + Router? _root; _ChainedRouter.empty(); - _ChainedRouter(Router root, Iterable middleware) { + _ChainedRouter(Router? root, Iterable middleware) { this._root = root; _handlers.addAll(middleware); } @override - Route addRoute(String method, String path, handler, - {Iterable middleware}) { - var route = super.addRoute(method, path, handler, + Route addRoute(String? method, String path, handler, + {Iterable? middleware}) { + Route route = super.addRoute(method, path, handler, middleware: []..addAll(_handlers)..addAll(middleware ?? [])); //_root._routes.add(route); return route; @@ -425,7 +425,7 @@ class _ChainedRouter extends Router { @override SymlinkRoute group(String path, void callback(Router router), - {Iterable middleware, String name}) { + {Iterable? middleware, String? name}) { final router = _ChainedRouter( _root, []..addAll(_handlers)..addAll(middleware ?? [])); callback(router); @@ -435,7 +435,7 @@ class _ChainedRouter extends Router { @override Future> groupAsync( String path, FutureOr callback(Router router), - {Iterable middleware, String name}) async { + {Iterable? middleware, String? name}) async { final router = _ChainedRouter( _root, []..addAll(_handlers)..addAll(middleware ?? [])); await callback(router); @@ -444,7 +444,7 @@ class _ChainedRouter extends Router { @override SymlinkRoute mount(String path, Router router) { - final route = super.mount(path, router); + final SymlinkRoute route = super.mount(path, router); route.router._middleware.insertAll(0, _handlers); //_root._routes.add(route); return route; @@ -473,13 +473,13 @@ Router flatten(Router router) { var path = route.path.replaceAll(_straySlashes, ''); var joined = '$base/$path'.replaceAll(_straySlashes, ''); flattened.addRoute(route.method, joined.replaceAll(_straySlashes, ''), - route.handlers.last, + route.handlers!.last, middleware: - route.handlers.take(route.handlers.length - 1).toList()); + route.handlers!.take(route.handlers!.length - 1).toList()); } } else { - flattened.addRoute(route.method, route.path, route.handlers.last, - middleware: route.handlers.take(route.handlers.length - 1).toList()); + flattened.addRoute(route.method, route.path, route.handlers!.last, + middleware: route.handlers!.take(route.handlers!.length - 1).toList()); } } diff --git a/packages/route/lib/src/routing_result.dart b/packages/route/lib/src/routing_result.dart index e3aafa69..5d7c4b2a 100644 --- a/packages/route/lib/src/routing_result.dart +++ b/packages/route/lib/src/routing_result.dart @@ -3,49 +3,49 @@ part of angel_route.src.router; /// Represents a complex result of navigating to a path. class RoutingResult { /// The parse result that matched the given sub-path. - final ParseResult parseResult; + final ParseResult? parseResult; /// A nested instance, if a sub-path was matched. - final Iterable> nested; + final Iterable>? nested; /// All route params matching this route on the current sub-path. - final Map params = {}; + final Map params = {}; /// The [Route] that answered this sub-path. /// /// This is mostly for internal use, and useless in production. - final Route shallowRoute; + final Route? shallowRoute; /// The [Router] that answered this sub-path. /// /// Only really for internal use. - final Router shallowRouter; + final Router? shallowRouter; /// The remainder of the full path that was not matched, and was passed to [nested] routes. final String tail; /// The [RoutingResult] that matched the most specific sub-path. RoutingResult get deepest { - var search = this; + RoutingResult search = this; while (search?.nested?.isNotEmpty == true) { - search = search.nested.first; + search = search.nested!.first; } return search; } /// The most specific route. - Route get route => deepest.shallowRoute; + Route? get route => deepest.shallowRoute; /// The most specific router. - Router get router => deepest.shallowRouter; + Router? get router => deepest.shallowRouter; /// The handlers at this sub-path. List get handlers { return [] - ..addAll(shallowRouter.middleware) - ..addAll(shallowRoute.handlers); + ..addAll(shallowRouter!.middleware) + ..addAll(shallowRoute!.handlers!); } /// All handlers on this sub-path and its children. @@ -56,7 +56,7 @@ class RoutingResult { handlers.addAll(result.handlers); if (result.nested?.isNotEmpty == true) { - for (var r in result.nested) { + for (var r in result.nested!) { crawl(r); } } @@ -68,14 +68,14 @@ class RoutingResult { } /// All parameters on this sub-path and its children. - Map get allParams { - final Map params = {}; + Map get allParams { + final Map params = {}; void crawl(RoutingResult result) { params.addAll(result.params); if (result.nested?.isNotEmpty == true) { - for (var r in result.nested) { + for (var r in result.nested!) { crawl(r); } } @@ -87,11 +87,11 @@ class RoutingResult { RoutingResult( {this.parseResult, - Map params = const {}, + Map params = const {}, this.nested, this.shallowRoute, this.shallowRouter, - @required this.tail}) { + required this.tail}) { this.params.addAll(params ?? {}); } } diff --git a/packages/route/pubspec.yaml b/packages/route/pubspec.yaml index 2f2874dd..404f7fda 100644 --- a/packages/route/pubspec.yaml +++ b/packages/route/pubspec.yaml @@ -1,14 +1,17 @@ name: angel_route description: A powerful, isomorphic routing library for Dart. It is mainly used in the Angel framework, but can be used in Flutter and on the Web. -version: 4.0.0 +version: 5.0.0 author: Tobe O homepage: https://github.com/angel-dart/angel_route +publish_to: none environment: - sdk: ">=2.10.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: - combinator: ^1.0.0 - #meta: ^1.0.0 - #path: ^1.0.0 + combinator: + git: + url: https://github.com/dukefirehawk/angel.git + ref: sdk-2.12.x_nnbd + path: packages/combinator string_scanner: ^1.0.0 dev_dependencies: build_runner: ^1.11.5 diff --git a/packages/route/test/chain_nest_test.dart b/packages/route/test/chain_nest_test.dart index f1dfd10c..64c5b75f 100644 --- a/packages/route/test/chain_nest_test.dart +++ b/packages/route/test/chain_nest_test.dart @@ -11,7 +11,7 @@ void main() { ..dumpTree(); test('nested route groups with chain', () { - var r = router.resolveAbsolute('/b/e/f')?.first?.route; + var r = router.resolveAbsolute('/b/e/f').first.route!; expect(r, isNotNull); expect(r.handlers, hasLength(4)); expect(r.handlers, equals(['a', 'c', 'd', 'g'])); diff --git a/packages/route/test/parse_test.dart b/packages/route/test/parse_test.dart index e85a5bac..baec7da9 100644 --- a/packages/route/test/parse_test.dart +++ b/packages/route/test/parse_test.dart @@ -7,9 +7,9 @@ void main() { ..get('/double/double:id', '') ..get('/num/num:id', ''); - num getId(String path) { + num? getId(String path) { var result = router.resolveAbsolute(path).first; - return result.allParams['id'] as num; + return result.allParams['id'] as num?; } test('parse', () { diff --git a/packages/route/test/server_test.dart b/packages/route/test/server_test.dart index 28850329..3ccbdd2b 100644 --- a/packages/route/test/server_test.dart +++ b/packages/route/test/server_test.dart @@ -9,11 +9,11 @@ const List> people = [ ]; void main() { - http.Client client; + http.Client? client; final router = Router(); - HttpServer server; - String url; + late HttpServer server; + String? url; router.get('/', (req, res) { res.write('Root'); @@ -87,12 +87,12 @@ void main() { router.resolveAbsolute(req.uri.toString(), method: req.method); final pipeline = MiddlewarePipeline(results); - if (pipeline.handlers.isEmpty) { + if (pipeline.handlers!.isEmpty) { res ..statusCode = 404 ..writeln('404 Not Found'); } else { - for (final handler in pipeline.handlers) { + for (final handler in pipeline.handlers!) { if (!((await handler(req, res)) as bool)) break; } } @@ -103,7 +103,7 @@ void main() { tearDown(() async { await server.close(force: true); - client.close(); + client!.close(); client = null; url = null; }); @@ -111,13 +111,13 @@ void main() { group('top-level', () { group('get', () { test('root', () async { - final res = await client.get(Uri.parse(url)); + final res = await client!.get(Uri.parse(url!)); print('Response: ${res.body}'); expect(res.body, equals('Root')); }); test('path', () async { - final res = await client.get(Uri.parse('$url/hello')); + final res = await client!.get(Uri.parse('$url/hello')); print('Response: ${res.body}'); expect(res.body, equals('World')); }); @@ -127,20 +127,20 @@ void main() { group('group', () { group('top-level', () { test('root', () async { - final res = await client.get(Uri.parse('$url/people')); + final res = await client!.get(Uri.parse('$url/people')); print('Response: ${res.body}'); expect(json.decode(res.body), equals(people)); }); group('param', () { test('root', () async { - final res = await client.get(Uri.parse('$url/people/0')); + final res = await client!.get(Uri.parse('$url/people/0')); print('Response: ${res.body}'); expect(json.decode(res.body), equals(people.first)); }); test('path', () async { - final res = await client.get(Uri.parse('$url/people/0/name')); + final res = await client!.get(Uri.parse('$url/people/0/name')); print('Response: ${res.body}'); expect(json.decode(res.body), equals(people.first['name'])); }); @@ -151,20 +151,20 @@ void main() { group('mount', () { group('path', () { test('top-level', () async { - final res = await client.post(Uri.parse('$url/beatles/spinal_clacker')); + final res = await client!.post(Uri.parse('$url/beatles/spinal_clacker')); print('Response: ${res.body}'); expect(res.body, equals('come together')); }); test('fallback', () async { - final res = await client.patch(Uri.parse('$url/beatles/muddy_water')); + final res = await client!.patch(Uri.parse('$url/beatles/muddy_water')); print('Response: ${res.body}'); expect(res.body, equals('together')); }); test('fallback', () async { final res = - await client.patch(Uri.parse('$url/beatles/spanil_clakcer')); + await client!.patch(Uri.parse('$url/beatles/spanil_clakcer')); print('Response: ${res.body}'); expect(res.body, equals('together')); }); @@ -172,7 +172,7 @@ void main() { test('deep nested', () async { final res = - await client.get(Uri.parse('$url/beatles/big/yellow/submarine')); + await client!.get(Uri.parse('$url/beatles/big/yellow/submarine')); print('Response: ${res.body}'); expect(res.body, equals('we all live in a')); }); @@ -187,17 +187,17 @@ void main() { }); test('path', () async { - await expect404(client.get(Uri.parse('$url/foo'))); - await expect404(client.get(Uri.parse('$url/bye'))); - await expect404(client.get(Uri.parse('$url/people/0/age'))); - await expect404(client.get(Uri.parse('$url/beatles2'))); + await expect404(client!.get(Uri.parse('$url/foo'))); + await expect404(client!.get(Uri.parse('$url/bye'))); + await expect404(client!.get(Uri.parse('$url/people/0/age'))); + await expect404(client!.get(Uri.parse('$url/beatles2'))); }); test('method', () async { - await expect404(client.head(Uri.parse(url))); - await expect404(client.patch(Uri.parse('$url/people'))); - await expect404(client.post(Uri.parse('$url/people/0'))); - await expect404(client.delete(Uri.parse('$url/beatles2/spinal_clacker'))); + await expect404(client!.head(Uri.parse(url!))); + await expect404(client!.patch(Uri.parse('$url/people'))); + await expect404(client!.post(Uri.parse('$url/people/0'))); + await expect404(client!.delete(Uri.parse('$url/beatles2/spinal_clacker'))); }); }); } diff --git a/packages/route/test/wildcard_test.dart b/packages/route/test/wildcard_test.dart index a49cfcf1..e974d51b 100644 --- a/packages/route/test/wildcard_test.dart +++ b/packages/route/test/wildcard_test.dart @@ -26,21 +26,21 @@ void main() { test('tail explicitly set intermediate', () { var results = router.resolveAbsolute('/songs/in_the/key'); var result = results.first; - print(results.map((r) => {r.route.path: r.tail})); + print(results.map((r) => {r.route!.path: r.tail})); expect(result.tail, 'in_the'); }); test('tail explicitly set at end', () { var results = router.resolveAbsolute('/isnt/she/epic'); var result = results.first; - print(results.map((r) => {r.route.path: r.tail})); + print(results.map((r) => {r.route!.path: r.tail})); expect(result.tail, 'epic'); }); test('tail with trailing', () { var results = router.resolveAbsolute('/isnt/she/epic/fail'); var result = results.first; - print(results.map((r) => {r.route.path: r.tail})); + print(results.map((r) => {r.route!.path: r.tail})); expect(result.tail, 'epic/fail'); }); } diff --git a/packages/route/web/shared/basic.dart b/packages/route/web/shared/basic.dart index c3b37074..3f00c120 100644 --- a/packages/route/web/shared/basic.dart +++ b/packages/route/web/shared/basic.dart @@ -9,15 +9,15 @@ basic(BrowserRouter router) { final route = result?.route; if (route == null) { - $h1.text = 'No Active Route'; - $ul.children + $h1!.text = 'No Active Route'; + $ul!.children ..clear() ..add(LIElement()..text = '(empty)'); } else { - $h1.text = 'Active Route: ${route.name ?? route.path}'; - $ul.children + $h1!.text = 'Active Route: ${route.name ?? route.path}'; + $ul!.children ..clear() - ..addAll(result.allHandlers + ..addAll(result!.allHandlers .map((handler) => LIElement()..text = handler.toString())); } }); From 5a1782efba4cbbc5adf32b8e5c6416e7d5f1e153 Mon Sep 17 00:00:00 2001 From: thomashii Date: Thu, 18 Mar 2021 08:15:01 +0800 Subject: [PATCH 007/171] Updated Angel Model to Null Safety --- CHANGELOG.md | 1 + packages/model/example/main.dart | 10 +++++----- packages/model/lib/angel_model.dart | 8 ++++---- packages/model/pubspec.yaml | 4 ++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abf107ed..2db3b2dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Added code_buffer 2.0.0 * Added combinator 2.0.0 * Updated angel_route to 5.0.0 +* Updated angel_model to 3.0.0 # 3.0.0 (Non NNBD) * Changed Dart SDK requirements for all packages to ">=2.10.0 <3.0.0" diff --git a/packages/model/example/main.dart b/packages/model/example/main.dart index f6c5caef..5da94558 100644 --- a/packages/model/example/main.dart +++ b/packages/model/example/main.dart @@ -6,15 +6,15 @@ void main() { } class Todo extends Model { - String text; + String? text; - bool isComplete; + bool? isComplete; Todo( - {String id, + {String? id, this.text, this.isComplete, - DateTime createdAt, - DateTime updatedAt}) + DateTime? createdAt, + DateTime? updatedAt}) : super(id: id, createdAt: createdAt, updatedAt: updatedAt); } diff --git a/packages/model/lib/angel_model.dart b/packages/model/lib/angel_model.dart index d179fa54..3da6c1eb 100644 --- a/packages/model/lib/angel_model.dart +++ b/packages/model/lib/angel_model.dart @@ -1,16 +1,16 @@ /// Represents arbitrary data, with an associated ID and timestamps. class Model { /// A unique identifier corresponding to this item. - String id; + String? id; /// The time at which this item was created. - DateTime createdAt; + DateTime? createdAt; /// The last time at which this item was updated. - DateTime updatedAt; + DateTime? updatedAt; Model({this.id, this.createdAt, this.updatedAt}); /// Returns the [id], parsed as an [int]. - int get idAsInt => id == null ? null : int.tryParse(id); + int? get idAsInt => id == null ? null : int.tryParse(id!); } diff --git a/packages/model/pubspec.yaml b/packages/model/pubspec.yaml index 25b77639..47a00fd5 100644 --- a/packages/model/pubspec.yaml +++ b/packages/model/pubspec.yaml @@ -1,9 +1,9 @@ name: angel_model -version: 2.0.0 +version: 3.0.0 description: Angel's basic data model class, no longer with the added weight of the whole framework. author: Tobe O homepage: https://github.com/dukefirehawk/angel/packages/model environment: - sdk: ">=2.10.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dev_dependencies: pedantic: ^1.11.0 From aefe1f1ab8bef61916e7677820fb0ed337c2e1c4 Mon Sep 17 00:00:00 2001 From: thomashii Date: Thu, 18 Mar 2021 08:21:42 +0800 Subject: [PATCH 008/171] Updated Angel Container to Null Safety --- CHANGELOG.md | 5 ++- .../angel_container/example/main.dart | 14 +++---- .../angel_container/lib/src/container.dart | 40 +++++++++---------- .../angel_container/lib/src/empty/empty.dart | 14 +++---- .../lib/src/mirrors/reflector.dart | 12 +++--- .../angel_container/lib/src/reflector.dart | 18 ++++----- .../lib/src/static/static.dart | 12 +++--- .../angel_container/lib/src/throwing.dart | 2 +- .../container/angel_container/pubspec.yaml | 8 ++-- .../angel_container/test/common.dart | 18 ++++----- .../angel_container/test/has_test.dart | 8 ++-- .../angel_container/test/named_test.dart | 6 +-- 12 files changed, 79 insertions(+), 78 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2db3b2dc..20accd9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,11 @@ * Updated pretty_logging to 3.0.0 * Updated angel_http_exception to 3.0.0 * Moved angel_cli to https://github.com/dukefirehawk/cli -* Added code_buffer 2.0.0 -* Added combinator 2.0.0 +* Added code_buffer and updated to 2.0.0 +* Added combinator and updated to 2.0.0 * Updated angel_route to 5.0.0 * Updated angel_model to 3.0.0 +* Updated angel_container to 3.0.0 # 3.0.0 (Non NNBD) * Changed Dart SDK requirements for all packages to ">=2.10.0 <3.0.0" diff --git a/packages/container/angel_container/example/main.dart b/packages/container/angel_container/example/main.dart index 3ee5106b..4dd22142 100644 --- a/packages/container/angel_container/example/main.dart +++ b/packages/container/angel_container/example/main.dart @@ -21,14 +21,14 @@ Future main() async { }); // Use `make` to create an instance. - var truck = container.make(); + var truck = container.make()!; // You can also resolve injections asynchronously. container.registerFactory>((_) async => 24); print(await container.makeAsync()); // Asynchronous resolution also works for plain objects. - await container.makeAsync().then((t) => t.drive()); + await container.makeAsync()!.then((t) => t.drive()); // Register a named singleton. container.registerNamedSingleton('the_truck', truck); @@ -37,7 +37,7 @@ Future main() async { truck.drive(); // Should print the same. - container.findByName('the_truck').drive(); + container.findByName('the_truck')!.drive(); // We can make a child container with its own factory. var childContainer = container.createChild(); @@ -47,10 +47,10 @@ Future main() async { }); // Make a truck with 5666 HP. - childContainer.make().drive(); + childContainer.make()!.drive(); // However, calling `make` will return the Engine singleton we created above. - print(childContainer.make().horsePower); + print(childContainer.make()!.horsePower); } abstract class Truck { @@ -64,12 +64,12 @@ class Engine { } class _TruckImpl implements Truck { - final Engine engine; + final Engine? engine; _TruckImpl(this.engine); @override void drive() { - print('Vroom! I have ${engine.horsePower} horsepower in my engine.'); + print('Vroom! I have ${engine!.horsePower} horsepower in my engine.'); } } diff --git a/packages/container/angel_container/lib/src/container.dart b/packages/container/angel_container/lib/src/container.dart index 3ab78bde..398aeae4 100644 --- a/packages/container/angel_container/lib/src/container.dart +++ b/packages/container/angel_container/lib/src/container.dart @@ -7,11 +7,11 @@ class Container { final Map _singletons = {}; final Map _factories = {}; final Map _namedSingletons = {}; - final Container _parent; + final Container? _parent; Container(this.reflector) : _parent = null; - Container._child(this._parent) : reflector = _parent.reflector; + Container._child(Container this._parent) : reflector = _parent.reflector; bool get isRoot => _parent == null; @@ -23,8 +23,8 @@ class Container { } /// Determines if the container has an injection of the given type. - bool has([Type t]) { - var search = this; + bool has([Type? t]) { + Container? search = this; t ??= T == dynamic ? t : T; while (search != null) { @@ -42,7 +42,7 @@ class Container { /// Determines if the container has a named singleton with the given [name]. bool hasNamed(String name) { - var search = this; + Container? search = this; while (search != null) { if (search._namedSingletons.containsKey(name)) { @@ -59,9 +59,9 @@ class Container { /// /// It is similar to [make], but resolves an injection of either /// `Future` or `T`. - Future makeAsync([Type type]) { + Future? makeAsync([Type? type]) { type ??= T; - Type futureType; //.Future.value(null).runtimeType; + Type? futureType; //.Future.value(null).runtimeType; if (T == dynamic) { try { @@ -87,18 +87,18 @@ class Container { /// /// In contexts where a static generic type cannot be used, use /// the [type] argument, instead of [T]. - T make([Type type]) { + T? make([Type? type]) { type ??= T; - var search = this; + Container? search = this; while (search != null) { if (search._singletons.containsKey(type)) { // Find a singleton, if any. - return search._singletons[type] as T; + return search._singletons[type] as T?; } else if (search._factories.containsKey(type)) { // Find a factory, if any. - return search._factories[type](this) as T; + return search._factories[type]!(this) as T?; } else { search = search._parent; } @@ -115,8 +115,8 @@ class Container { var constructor = reflectedType.constructors.firstWhere( (c) => isDefault(c.name), - orElse: () => throw ReflectionException( - '${reflectedType.name} has no default constructor, and therefore cannot be instantiated.')); + orElse: (() => throw ReflectionException( + '${reflectedType.name} has no default constructor, and therefore cannot be instantiated.')) as ReflectedFunction Function()?); for (var param in constructor.parameters) { var value = make(param.type.reflectedType); @@ -131,7 +131,7 @@ class Container { return reflectedType.newInstance( isDefault(constructor.name) ? '' : constructor.name, positional, - named, []).reflectee as T; + named, []).reflectee as T?; } else { throw ReflectionException( '$type is not a class, and therefore cannot be instantiated.'); @@ -144,7 +144,7 @@ class Container { /// /// Returns [f]. T Function(Container) registerLazySingleton(T Function(Container) f, - {Type as}) { + {Type? as}) { return registerFactory( (container) { var r = f(container); @@ -159,7 +159,7 @@ class Container { /// type within *this* container will return the result of [f]. /// /// Returns [f]. - T Function(Container) registerFactory(T Function(Container) f, {Type as}) { + T Function(Container) registerFactory(T Function(Container) f, {Type? as}) { as ??= T; if (_factories.containsKey(as)) { @@ -174,7 +174,7 @@ class Container { /// type within *this* container will return [object]. /// /// Returns [object]. - T registerSingleton(T object, {Type as}) { + T registerSingleton(T object, {Type? as}) { as ??= T == dynamic ? as : T; if (_singletons.containsKey(as ?? object.runtimeType)) { @@ -192,11 +192,11 @@ class Container { /// /// [findByName] is best reserved for internal logic that end users of code should /// not see. - T findByName(String name) { + T? findByName(String name) { if (_namedSingletons.containsKey(name)) { - return _namedSingletons[name] as T; + return _namedSingletons[name] as T?; } else if (_parent != null) { - return _parent.findByName(name); + return _parent!.findByName(name); } else { throw StateError( 'This container does not have a singleton named "$name".'); diff --git a/packages/container/angel_container/lib/src/empty/empty.dart b/packages/container/angel_container/lib/src/empty/empty.dart index 9a8bb221..de78c2e1 100644 --- a/packages/container/angel_container/lib/src/empty/empty.dart +++ b/packages/container/angel_container/lib/src/empty/empty.dart @@ -1,6 +1,6 @@ import '../../angel_container.dart'; -final Map _symbolNames = {}; +final Map _symbolNames = {}; /// A [Reflector] implementation that performs no actual reflection, /// instead returning empty objects on every invocation. @@ -13,9 +13,9 @@ class EmptyReflector extends Reflector { const EmptyReflector(); @override - String getName(Symbol symbol) { + String? getName(Symbol symbol) { return _symbolNames.putIfAbsent( - symbol, () => symbolRegex.firstMatch(symbol.toString()).group(1)); + symbol, () => symbolRegex.firstMatch(symbol.toString())!.group(1)); } @override @@ -52,13 +52,13 @@ class _EmptyReflectedClass extends ReflectedClass { @override ReflectedInstance newInstance( String constructorName, List positionalArguments, - [Map namedArguments, List typeArguments]) { + [Map? namedArguments, List? typeArguments]) { throw UnsupportedError( 'Classes reflected via an EmptyReflector cannot be instantiated.'); } @override - bool isAssignableTo(ReflectedType other) { + bool isAssignableTo(ReflectedType? other) { return other == this; } @@ -75,13 +75,13 @@ class _EmptyReflectedType extends ReflectedType { @override ReflectedInstance newInstance( String constructorName, List positionalArguments, - [Map namedArguments, List typeArguments]) { + [Map? namedArguments, List? typeArguments]) { throw UnsupportedError( 'Types reflected via an EmptyReflector cannot be instantiated.'); } @override - bool isAssignableTo(ReflectedType other) { + bool isAssignableTo(ReflectedType? other) { return other == this; } diff --git a/packages/container/angel_container/lib/src/mirrors/reflector.dart b/packages/container/angel_container/lib/src/mirrors/reflector.dart index 74639a68..d3d26835 100644 --- a/packages/container/angel_container/lib/src/mirrors/reflector.dart +++ b/packages/container/angel_container/lib/src/mirrors/reflector.dart @@ -85,7 +85,7 @@ class _ReflectedTypeMirror extends ReflectedType { ); @override - bool isAssignableTo(ReflectedType other) { + bool isAssignableTo(ReflectedType? other) { if (other is _ReflectedClassMirror) { return mirror.isAssignableTo(other.mirror); } else if (other is _ReflectedTypeMirror) { @@ -98,7 +98,7 @@ class _ReflectedTypeMirror extends ReflectedType { @override ReflectedInstance newInstance( String constructorName, List positionalArguments, - [Map namedArguments, List typeArguments]) { + [Map? namedArguments, List? typeArguments]) { throw ReflectionException( '$name is not a class, and therefore cannot be instantiated.'); } @@ -154,7 +154,7 @@ class _ReflectedClassMirror extends ReflectedClass { List get constructors => _constructorsOf(mirror); @override - bool isAssignableTo(ReflectedType other) { + bool isAssignableTo(ReflectedType? other) { if (other is _ReflectedClassMirror) { return mirror.isAssignableTo(other.mirror); } else if (other is _ReflectedTypeMirror) { @@ -167,7 +167,7 @@ class _ReflectedClassMirror extends ReflectedClass { @override ReflectedInstance newInstance( String constructorName, List positionalArguments, - [Map namedArguments, List typeArguments]) { + [Map? namedArguments, List? typeArguments]) { return _ReflectedInstanceMirror( mirror.newInstance(Symbol(constructorName), positionalArguments)); } @@ -207,7 +207,7 @@ class _ReflectedInstanceMirror extends ReflectedInstance { class _ReflectedMethodMirror extends ReflectedFunction { final dart.MethodMirror mirror; - final dart.ClosureMirror closureMirror; + final dart.ClosureMirror? closureMirror; _ReflectedMethodMirror(this.mirror, [this.closureMirror]) : super( @@ -242,7 +242,7 @@ class _ReflectedMethodMirror extends ReflectedFunction { 'This object was reflected without a ClosureMirror, and therefore cannot be directly invoked.'); } - return _ReflectedInstanceMirror(closureMirror.invoke(invocation.memberName, + return _ReflectedInstanceMirror(closureMirror!.invoke(invocation.memberName, invocation.positionalArguments, invocation.namedArguments)); } } diff --git a/packages/container/angel_container/lib/src/reflector.dart b/packages/container/angel_container/lib/src/reflector.dart index 1220a5ab..dfa24be8 100644 --- a/packages/container/angel_container/lib/src/reflector.dart +++ b/packages/container/angel_container/lib/src/reflector.dart @@ -4,15 +4,15 @@ import 'package:quiver/core.dart'; abstract class Reflector { const Reflector(); - String getName(Symbol symbol); + String? getName(Symbol symbol); - ReflectedClass reflectClass(Type clazz); + ReflectedClass? reflectClass(Type clazz); - ReflectedFunction reflectFunction(Function function); + ReflectedFunction? reflectFunction(Function function); - ReflectedType reflectType(Type type); + ReflectedType? reflectType(Type type); - ReflectedInstance reflectInstance(Object object); + ReflectedInstance? reflectInstance(Object object); ReflectedType reflectFutureOf(Type type) { throw UnsupportedError('`reflectFutureOf` requires `dart:mirrors`.'); @@ -22,7 +22,7 @@ abstract class Reflector { abstract class ReflectedInstance { final ReflectedType type; final ReflectedClass clazz; - final Object reflectee; + final Object? reflectee; const ReflectedInstance(this.type, this.clazz, this.reflectee); @@ -56,9 +56,9 @@ abstract class ReflectedType { ReflectedInstance newInstance( String constructorName, List positionalArguments, - [Map namedArguments, List typeArguments]); + [Map? namedArguments, List? typeArguments]); - bool isAssignableTo(ReflectedType other); + bool isAssignableTo(ReflectedType? other); } abstract class ReflectedClass extends ReflectedType { @@ -94,7 +94,7 @@ abstract class ReflectedClass extends ReflectedType { class ReflectedDeclaration { final String name; final bool isStatic; - final ReflectedFunction function; + final ReflectedFunction? function; const ReflectedDeclaration(this.name, this.isStatic, this.function); diff --git a/packages/container/angel_container/lib/src/static/static.dart b/packages/container/angel_container/lib/src/static/static.dart index 645a9fa5..0a915265 100644 --- a/packages/container/angel_container/lib/src/static/static.dart +++ b/packages/container/angel_container/lib/src/static/static.dart @@ -16,7 +16,7 @@ class StaticReflector extends Reflector { this.instances = const {}}); @override - String getName(Symbol symbol) { + String? getName(Symbol symbol) { if (!names.containsKey(symbol)) { throw ArgumentError( 'The value of $symbol is unknown - it was not generated.'); @@ -26,11 +26,11 @@ class StaticReflector extends Reflector { } @override - ReflectedClass reflectClass(Type clazz) => - reflectType(clazz) as ReflectedClass; + ReflectedClass? reflectClass(Type clazz) => + reflectType(clazz) as ReflectedClass?; @override - ReflectedFunction reflectFunction(Function function) { + ReflectedFunction? reflectFunction(Function function) { if (!functions.containsKey(function)) { throw ArgumentError( 'There is no reflection information available about $function.'); @@ -40,7 +40,7 @@ class StaticReflector extends Reflector { } @override - ReflectedInstance reflectInstance(Object object) { + ReflectedInstance? reflectInstance(Object object) { if (!instances.containsKey(object)) { throw ArgumentError( 'There is no reflection information available about $object.'); @@ -50,7 +50,7 @@ class StaticReflector extends Reflector { } @override - ReflectedType reflectType(Type type) { + ReflectedType? reflectType(Type type) { if (!types.containsKey(type)) { throw ArgumentError( 'There is no reflection information available about $type.'); diff --git a/packages/container/angel_container/lib/src/throwing.dart b/packages/container/angel_container/lib/src/throwing.dart index 05a2ea86..5b918fbd 100644 --- a/packages/container/angel_container/lib/src/throwing.dart +++ b/packages/container/angel_container/lib/src/throwing.dart @@ -17,7 +17,7 @@ class ThrowingReflector extends Reflector { const ThrowingReflector({this.errorMessage = defaultErrorMessage}); @override - String getName(Symbol symbol) => const EmptyReflector().getName(symbol); + String? getName(Symbol symbol) => const EmptyReflector().getName(symbol); UnsupportedError _error() => UnsupportedError(errorMessage); diff --git a/packages/container/angel_container/pubspec.yaml b/packages/container/angel_container/pubspec.yaml index d2b83669..6451f5d9 100644 --- a/packages/container/angel_container/pubspec.yaml +++ b/packages/container/angel_container/pubspec.yaml @@ -1,13 +1,13 @@ name: angel_container -version: 2.0.0 +version: 3.0.0 author: Tobe O description: A hierarchical DI container, and pluggable backends for reflection. homepage: https://github.com/angel-dart/container.git environment: - sdk: ">=2.10.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: collection: ^1.15.0 - quiver: ^2.1.5 + quiver: ^3.0.0 dev_dependencies: pedantic: ^1.11.0 - test: ^1.16.5 \ No newline at end of file + test: ^1.16.8 \ No newline at end of file diff --git a/packages/container/angel_container/test/common.dart b/packages/container/angel_container/test/common.dart index b638ac9c..c070a594 100644 --- a/packages/container/angel_container/test/common.dart +++ b/packages/container/angel_container/test/common.dart @@ -7,7 +7,7 @@ void returnVoidFromAFunction(int x) {} void testReflector(Reflector reflector) { var blaziken = Pokemon('Blaziken', PokemonType.fire); - Container container; + late Container container; setUp(() { container = Container(reflector); @@ -16,7 +16,7 @@ void testReflector(Reflector reflector) { }); test('get field', () { - var blazikenMirror = reflector.reflectInstance(blaziken); + var blazikenMirror = reflector.reflectInstance(blaziken)!; expect(blazikenMirror.getField('type').reflectee, blaziken.type); }); @@ -24,19 +24,19 @@ void testReflector(Reflector reflector) { var mirror = reflector.reflectFunction(returnVoidFromAFunction); test('void return type returns dynamic', () { - expect(mirror.returnType, reflector.reflectType(dynamic)); + expect(mirror!.returnType, reflector.reflectType(dynamic)); }); test('counts parameters', () { - expect(mirror.parameters, hasLength(1)); + expect(mirror!.parameters, hasLength(1)); }); test('counts types parameters', () { - expect(mirror.typeParameters, isEmpty); + expect(mirror!.typeParameters, isEmpty); }); test('correctly reflects parameter types', () { - var p = mirror.parameters[0]; + var p = mirror!.parameters[0]; expect(p.name, 'x'); expect(p.isRequired, true); expect(p.isNamed, false); @@ -67,12 +67,12 @@ void testReflector(Reflector reflector) { }); test('constructor injects singleton', () { - var lower = container.make(); + var lower = container.make()!; expect(lower.lowercaseName, blaziken.name.toLowerCase()); }); test('newInstance works', () { - var type = container.reflector.reflectType(Pokemon); + var type = container.reflector.reflectType(Pokemon)!; var instance = type.newInstance('changeName', [blaziken, 'Charizard']).reflectee as Pokemon; @@ -83,7 +83,7 @@ void testReflector(Reflector reflector) { test('isAssignableTo', () { var pokemonType = container.reflector.reflectType(Pokemon); - var kantoPokemonType = container.reflector.reflectType(KantoPokemon); + var kantoPokemonType = container.reflector.reflectType(KantoPokemon)!; expect(kantoPokemonType.isAssignableTo(pokemonType), true); expect( diff --git a/packages/container/angel_container/test/has_test.dart b/packages/container/angel_container/test/has_test.dart index bd5d21cb..762e56a0 100644 --- a/packages/container/angel_container/test/has_test.dart +++ b/packages/container/angel_container/test/has_test.dart @@ -2,7 +2,7 @@ import 'package:angel_container/angel_container.dart'; import 'package:test/test.dart'; void main() { - Container container; + late Container container; setUp(() { container = Container(const EmptyReflector()) @@ -37,14 +37,14 @@ void main() { } class Artist { - final String name; - final Song song; + final String? name; + final Song? song; Artist({this.name, this.song}); } class Song { - final String title; + final String? title; Song({this.title}); } diff --git a/packages/container/angel_container/test/named_test.dart b/packages/container/angel_container/test/named_test.dart index e4c80079..d6d46711 100644 --- a/packages/container/angel_container/test/named_test.dart +++ b/packages/container/angel_container/test/named_test.dart @@ -2,7 +2,7 @@ import 'package:angel_container/angel_container.dart'; import 'package:test/test.dart'; void main() { - Container container; + late Container container; setUp(() { container = Container(const EmptyReflector()); @@ -10,7 +10,7 @@ void main() { }); test('fetch by name', () { - expect(container.findByName('foo').bar, 'baz'); + expect(container.findByName('foo')!.bar, 'baz'); }); test('cannot redefine', () { @@ -28,7 +28,7 @@ void main() { } class Foo { - final String bar; + final String? bar; Foo({this.bar}); } From f8ec10d4e2d555538f2b2bcd9f5d70a9fee67ced Mon Sep 17 00:00:00 2001 From: thomashii Date: Fri, 19 Mar 2021 07:10:19 +0800 Subject: [PATCH 009/171] Added merge_map and mock_request packages --- CHANGELOG.md | 3 + packages/framework/pubspec.yaml | 16 +- packages/merge_map/.gitignore | 76 +++++ packages/merge_map/CHANGELOG.md | 7 + packages/merge_map/LICENSE | 21 ++ packages/merge_map/README.md | 19 ++ packages/merge_map/example/main.dart | 11 + packages/merge_map/lib/merge_map.dart | 34 ++ packages/merge_map/pubspec.yaml | 9 + packages/merge_map/test/all_test.dart | 94 ++++++ packages/mock_request/.gitignore | 72 ++++ packages/mock_request/.travis.yml | 1 + packages/mock_request/CHANGELOG.md | 17 + packages/mock_request/LICENSE | 21 ++ packages/mock_request/README.md | 25 ++ packages/mock_request/analysis_options.yaml | 4 + packages/mock_request/example/main.dart | 7 + packages/mock_request/lib/mock_request.dart | 6 + .../mock_request/lib/src/connection_info.dart | 10 + packages/mock_request/lib/src/headers.dart | 151 +++++++++ .../lib/src/lockable_headers.dart | 67 ++++ packages/mock_request/lib/src/request.dart | 317 ++++++++++++++++++ packages/mock_request/lib/src/response.dart | 150 +++++++++ packages/mock_request/lib/src/session.dart | 74 ++++ packages/mock_request/pubspec.yaml | 13 + packages/mock_request/test/all_test.dart | 66 ++++ 26 files changed, 1285 insertions(+), 6 deletions(-) create mode 100644 packages/merge_map/.gitignore create mode 100644 packages/merge_map/CHANGELOG.md create mode 100644 packages/merge_map/LICENSE create mode 100644 packages/merge_map/README.md create mode 100644 packages/merge_map/example/main.dart create mode 100644 packages/merge_map/lib/merge_map.dart create mode 100644 packages/merge_map/pubspec.yaml create mode 100644 packages/merge_map/test/all_test.dart create mode 100644 packages/mock_request/.gitignore create mode 100644 packages/mock_request/.travis.yml create mode 100644 packages/mock_request/CHANGELOG.md create mode 100644 packages/mock_request/LICENSE create mode 100644 packages/mock_request/README.md create mode 100644 packages/mock_request/analysis_options.yaml create mode 100644 packages/mock_request/example/main.dart create mode 100644 packages/mock_request/lib/mock_request.dart create mode 100644 packages/mock_request/lib/src/connection_info.dart create mode 100644 packages/mock_request/lib/src/headers.dart create mode 100644 packages/mock_request/lib/src/lockable_headers.dart create mode 100644 packages/mock_request/lib/src/request.dart create mode 100644 packages/mock_request/lib/src/response.dart create mode 100644 packages/mock_request/lib/src/session.dart create mode 100644 packages/mock_request/pubspec.yaml create mode 100644 packages/mock_request/test/all_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 20accd9f..d51e4858 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ * Updated angel_route to 5.0.0 * Updated angel_model to 3.0.0 * Updated angel_container to 3.0.0 +* Updated angel_framework to 4.0.0 + - merge_map + - mock_request # 3.0.0 (Non NNBD) * Changed Dart SDK requirements for all packages to ">=2.10.0 <3.0.0" diff --git a/packages/framework/pubspec.yaml b/packages/framework/pubspec.yaml index 9f41c7ae..18d6acb0 100644 --- a/packages/framework/pubspec.yaml +++ b/packages/framework/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_framework -version: 3.0.0 +version: 4.0.0 description: A high-powered HTTP server with dependency injection, routing and much more. author: Tobe O homepage: https://github.com/angel-dart/angel_framework @@ -10,25 +10,29 @@ dependencies: angel_container: git: url: https://github.com/dukefirehawk/angel.git - ref: sdk-2.12.x + ref: sdk-2.12.x_nnbd path: packages/container/angel_container 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 angel_model: git: url: https://github.com/dukefirehawk/angel.git - ref: sdk-2.12.x + ref: sdk-2.12.x_nnbd path: packages/model angel_route: git: url: https://github.com/dukefirehawk/angel.git - ref: sdk-2.12.x + ref: sdk-2.12.x_nnbd path: packages/route charcode: ^1.0.0 - combinator: ^1.0.0 + combinator: + git: + url: https://github.com/dukefirehawk/angel.git + ref: sdk-2.12.x_nnbd + path: packages/combinator file: ^6.1.0 http_parser: ^4.0.0 http_server: ^0.9.0 diff --git a/packages/merge_map/.gitignore b/packages/merge_map/.gitignore new file mode 100644 index 00000000..3707b1e0 --- /dev/null +++ b/packages/merge_map/.gitignore @@ -0,0 +1,76 @@ +# Created by .ignore support plugin (hsz.mobi) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/workspace.xml +.idea/tasks.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Dart template +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.buildlog +.packages +.project +.pub/ +build/ +**/packages/ + +# Files created by dart2js +# (Most Dart developers will use pub build to compile Dart, use/modify these +# rules if you intend to use dart2js directly +# Convention is to use extension '.dart.js' for Dart compiled to Javascript to +# differentiate from explicit Javascript files) +*.dart.js +*.part.js +*.js.deps +*.js.map +*.info.json + +# Directory created by dartdoc +doc/api/ + +# Don't commit pubspec lock file +# (Library packages only! Remove pattern if developing an application package) +pubspec.lock + +.dart_tool diff --git a/packages/merge_map/CHANGELOG.md b/packages/merge_map/CHANGELOG.md new file mode 100644 index 00000000..b57e6a38 --- /dev/null +++ b/packages/merge_map/CHANGELOG.md @@ -0,0 +1,7 @@ +# 1.0.2 +* Add an example, for Pub's sake. + +# 1.0.1 +* Add a specific constraint on Dart versions, to prevent Pub from rejecting all packages that depend on +`merge_map` (the entire Angel framework). +* Add generic type support diff --git a/packages/merge_map/LICENSE b/packages/merge_map/LICENSE new file mode 100644 index 00000000..5ee3381f --- /dev/null +++ b/packages/merge_map/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Tobe O + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/merge_map/README.md b/packages/merge_map/README.md new file mode 100644 index 00000000..dcc3cfeb --- /dev/null +++ b/packages/merge_map/README.md @@ -0,0 +1,19 @@ +# merge_map +Combine multiple Maps into one. Equivalent to +[Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) +in JS. + +# Example + +```dart +import "package:merge_map/merge_map.dart"; + +main() { + Map map1 = {'hello': 'world'}; + Map map2 = {'foo': {'bar': 'baz', 'this': 'will be overwritten'}}; + Map map3 = {'foo': {'john': 'doe', 'this': 'overrides previous maps'}}; + Map merged = mergeMap(map1, map2, map3); + + // {hello: world, foo: {bar: baz, john: doe, this: overrides previous maps}} +} +``` \ No newline at end of file diff --git a/packages/merge_map/example/main.dart b/packages/merge_map/example/main.dart new file mode 100644 index 00000000..00b74c0d --- /dev/null +++ b/packages/merge_map/example/main.dart @@ -0,0 +1,11 @@ +import 'package:merge_map/merge_map.dart'; + +main() { + Map map1 = {'hello': 'world'}; + Map map2 = {'foo': {'bar': 'baz', 'this': 'will be overwritten'}}; + Map map3 = {'foo': {'john': 'doe', 'this': 'overrides previous maps'}}; + Map merged = mergeMap([map1, map2, map3]); + print(merged); + + // {hello: world, foo: {bar: baz, john: doe, this: overrides previous maps}} +} \ No newline at end of file diff --git a/packages/merge_map/lib/merge_map.dart b/packages/merge_map/lib/merge_map.dart new file mode 100644 index 00000000..c9d20ebf --- /dev/null +++ b/packages/merge_map/lib/merge_map.dart @@ -0,0 +1,34 @@ +/// Exposes the [mergeMap] function, which... merges Maps. +library merge_map; + +_copyValues( + Map from, Map? to, bool recursive, bool acceptNull) { + for (var key in from.keys) { + if (from[key] is Map && recursive) { + if (!(to![key] is Map)) { + to[key] = {} as V; + } + _copyValues(from[key] as Map, to[key] as Map?, recursive, acceptNull); + } else { + if (from[key] != null || acceptNull) to![key] = from[key]; + } + } +} + +/// Merges the values of the given maps together. +/// +/// `recursive` is set to `true` by default. If set to `true`, +/// then nested maps will also be merged. Otherwise, nested maps +/// will overwrite others. +/// +/// `acceptNull` is set to `false` by default. If set to `false`, +/// then if the value on a map is `null`, it will be ignored, and +/// that `null` will not be copied. +Map mergeMap(Iterable> maps, + {bool recursive: true, bool acceptNull: false}) { + Map result = {}; + maps.forEach((Map map) { + if (map != null) _copyValues(map, result, recursive, acceptNull); + }); + return result; +} diff --git a/packages/merge_map/pubspec.yaml b/packages/merge_map/pubspec.yaml new file mode 100644 index 00000000..c23b39fb --- /dev/null +++ b/packages/merge_map/pubspec.yaml @@ -0,0 +1,9 @@ +name: merge_map +description: Combine multiple Maps into one. Equivalent to Object.assign in JS. +version: 3.0.0 +homepage: https://github.com/thosakwe/merge_map +author: Tobe O +environment: + sdk: '>=2.12.0 <3.0.0' +dev_dependencies: + test: ^1.16.8 diff --git a/packages/merge_map/test/all_test.dart b/packages/merge_map/test/all_test.dart new file mode 100644 index 00000000..6ce20bd0 --- /dev/null +++ b/packages/merge_map/test/all_test.dart @@ -0,0 +1,94 @@ +import "package:merge_map/merge_map.dart"; +import "package:test/test.dart"; + +void main() { + test('can merge two simple maps', () { + Map merged = mergeMap([ + {'hello': 'world'}, + {'hello': 'dolly'} + ]); + expect(merged['hello'], equals('dolly')); + }); + + test("the last map's values supersede those of prior", () { + Map merged = mergeMap([ + {'letter': 'a'}, + {'letter': 'b'}, + {'letter': 'c'} + ]); + expect(merged['letter'], equals('c')); + }); + + test("can merge two once-nested maps", () { + Map map1 = { + 'hello': 'world', + 'foo': {'nested': false} + }; + Map map2 = { + 'goodbye': 'sad life', + 'foo': {'nested': true, 'it': 'works'} + }; + Map merged = mergeMap([map1, map2]); + + expect(merged['hello'], equals('world')); + expect(merged['goodbye'], equals('sad life')); + expect(merged['foo']['nested'], equals(true)); + expect(merged['foo']['it'], equals('works')); + }); + + test("once-nested map supersession", () { + Map map1 = { + 'hello': 'world', + 'foo': {'nested': false} + }; + Map map2 = { + 'goodbye': 'sad life', + 'foo': {'nested': true, 'it': 'works'} + }; + Map map3 = { + 'foo': {'nested': 'supersession'} + }; + + Map merged = mergeMap([map1, map2, map3]); + expect(merged['foo']['nested'], equals('supersession')); + }); + + test("can merge two twice-nested maps", () { + Map map1 = { + 'a': { + 'b': {'c': 'd'} + } + }; + Map map2 = { + 'a': { + 'b': {'c': 'D', 'e': 'f'} + } + }; + Map merged = mergeMap([map1, map2]); + + expect(merged['a']['b']['c'], equals('D')); + expect(merged['a']['b']['e'], equals('f')); + }); + + test("twice-nested map supersession", () { + Map map1 = { + 'a': { + 'b': {'c': 'd'} + } + }; + Map map2 = { + 'a': { + 'b': {'c': 'D', 'e': 'f'} + } + }; + Map map3 = { + 'a': { + 'b': {'e': 'supersession'} + } + }; + Map merged = mergeMap([map1, map2, map3]); + + expect(merged['a']['b']['c'], equals('D')); + expect(merged['a']['b']['e'], equals('supersession')); + }); +} diff --git a/packages/mock_request/.gitignore b/packages/mock_request/.gitignore new file mode 100644 index 00000000..8fff3906 --- /dev/null +++ b/packages/mock_request/.gitignore @@ -0,0 +1,72 @@ +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.buildlog +.packages +.project +.pub/ +build/ +**/packages/ + +# Files created by dart2js +# (Most Dart developers will use pub build to compile Dart, use/modify these +# rules if you intend to use dart2js directly +# Convention is to use extension '.dart.js' for Dart compiled to Javascript to +# differentiate from explicit Javascript files) +*.dart.js +*.part.js +*.js.deps +*.js.map +*.info.json + +# Directory created by dartdoc +doc/api/ + +# Don't commit pubspec lock file +# (Library packages only! Remove pattern if developing an application package) +pubspec.lock +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +.dart_tool \ No newline at end of file diff --git a/packages/mock_request/.travis.yml b/packages/mock_request/.travis.yml new file mode 100644 index 00000000..de2210c9 --- /dev/null +++ b/packages/mock_request/.travis.yml @@ -0,0 +1 @@ +language: dart \ No newline at end of file diff --git a/packages/mock_request/CHANGELOG.md b/packages/mock_request/CHANGELOG.md new file mode 100644 index 00000000..f52fee60 --- /dev/null +++ b/packages/mock_request/CHANGELOG.md @@ -0,0 +1,17 @@ +# 1.0.7 +* Prepare for upcoming Dart SDK change where `HttpHeaders` methods +`add` and `set` take an additional optional parameter `preserveHeaderCase` (thanks @domesticmouse!). + +# 1.0.6 +* Prepare for upcoming Dart SDK change whereby `HttpRequest` implements + `Stream` rather than `Stream>`. + +# 1.0.5 +* Add `toString` to `MockHttpHeaders`. + +# 1.0.4 +* Fix for `ifModifiedSince` + +# 1.0.3 +* Dart2 fixes +* Apparently fix hangs that break Angel tests diff --git a/packages/mock_request/LICENSE b/packages/mock_request/LICENSE new file mode 100644 index 00000000..3de28325 --- /dev/null +++ b/packages/mock_request/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Tobe O + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/mock_request/README.md b/packages/mock_request/README.md new file mode 100644 index 00000000..8074b907 --- /dev/null +++ b/packages/mock_request/README.md @@ -0,0 +1,25 @@ +# mock_request + +[![Pub](https://img.shields.io/pub/v/mock_request.svg)](https://pub.dartlang.org/packages/mock_request) +[![build status](https://travis-ci.org/thosakwe/mock_request.svg)](https://travis-ci.org/thosakwe/mock_request) + +Manufacture dart:io HttpRequests, HttpResponses, HttpHeaders, etc. +This makes it possible to test server-side Dart applications without +having to ever bind to a port. + +This package was originally designed to testing +[Angel](https://github.com/angel-dart/angel/wiki) +applications smoother, but works with any Dart-based server. :) + +# Usage +```dart +var rq = new MockHttpRequest('GET', Uri.parse('/foo')); +await rq.close(); +await app.handleRequest(rq); // Run within your server-side application +var rs = rq.response; +expect(rs.statusCode, equals(200)); +expect(await rs.transform(UTF8.decoder).join(), + equals(JSON.encode('Hello, world!'))); +``` + +More examples can be found in the included tests. \ No newline at end of file diff --git a/packages/mock_request/analysis_options.yaml b/packages/mock_request/analysis_options.yaml new file mode 100644 index 00000000..c230cee7 --- /dev/null +++ b/packages/mock_request/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:pedantic/analysis_options.yaml +analyzer: + strong-mode: + implicit-casts: false \ No newline at end of file diff --git a/packages/mock_request/example/main.dart b/packages/mock_request/example/main.dart new file mode 100644 index 00000000..152ab0ed --- /dev/null +++ b/packages/mock_request/example/main.dart @@ -0,0 +1,7 @@ +import 'dart:async'; +import 'package:mock_request/mock_request.dart'; + +Future main() async { + var rq = MockHttpRequest('GET', Uri.parse('/foo')); + await rq.close(); +} diff --git a/packages/mock_request/lib/mock_request.dart b/packages/mock_request/lib/mock_request.dart new file mode 100644 index 00000000..6cf677fa --- /dev/null +++ b/packages/mock_request/lib/mock_request.dart @@ -0,0 +1,6 @@ +export 'src/connection_info.dart'; +export 'src/headers.dart'; +export 'src/lockable_headers.dart'; +export 'src/request.dart'; +export 'src/response.dart'; +export 'src/session.dart'; diff --git a/packages/mock_request/lib/src/connection_info.dart b/packages/mock_request/lib/src/connection_info.dart new file mode 100644 index 00000000..ba7abe88 --- /dev/null +++ b/packages/mock_request/lib/src/connection_info.dart @@ -0,0 +1,10 @@ +import 'dart:io'; + +class MockHttpConnectionInfo implements HttpConnectionInfo { + @override + final InternetAddress remoteAddress; + @override + final int localPort, remotePort; + + MockHttpConnectionInfo({this.remoteAddress, this.localPort, this.remotePort}); +} diff --git a/packages/mock_request/lib/src/headers.dart b/packages/mock_request/lib/src/headers.dart new file mode 100644 index 00000000..913d1f1d --- /dev/null +++ b/packages/mock_request/lib/src/headers.dart @@ -0,0 +1,151 @@ +import 'dart:io'; + +class MockHttpHeaders extends HttpHeaders { + final Map> _data = {}; + final List _noFolding = []; + Uri _host; + + List get doNotFold => List.unmodifiable(_noFolding); + + @override + ContentType get contentType { + if (_data.containsKey(HttpHeaders.contentTypeHeader)) { + return ContentType.parse(_data[HttpHeaders.contentTypeHeader].join(',')); + } else { + return null; + } + } + + @override + set contentType(ContentType value) => + set(HttpHeaders.contentTypeHeader, value.value); + + @override + DateTime get date => _data.containsKey(HttpHeaders.dateHeader) + ? HttpDate.parse(_data[HttpHeaders.dateHeader].join(',')) + : null; + + @override + set date(DateTime value) => + set(HttpHeaders.dateHeader, HttpDate.format(value)); + + @override + DateTime get expires => _data.containsKey(HttpHeaders.expiresHeader) + ? HttpDate.parse(_data[HttpHeaders.expiresHeader].join(',')) + : null; + + @override + set expires(DateTime value) => + set(HttpHeaders.expiresHeader, HttpDate.format(value)); + + @override + DateTime get ifModifiedSince => + _data.containsKey(HttpHeaders.ifModifiedSinceHeader) + ? HttpDate.parse(_data[HttpHeaders.ifModifiedSinceHeader].join(',')) + : null; + + @override + set ifModifiedSince(DateTime value) => + set(HttpHeaders.ifModifiedSinceHeader, HttpDate.format(value)); + + @override + String get host { + if (_host != null) { + return _host.host; + } else if (_data.containsKey(HttpHeaders.hostHeader)) { + _host = Uri.parse(_data[HttpHeaders.hostHeader].join(',')); + return _host.host; + } else { + return null; + } + } + + @override + int get port { + host; // Parse it + return _host?.port; + } + + @override + List operator [](String name) => _data[name.toLowerCase()]; + + @override + void add(String name, Object value, {bool preserveHeaderCase = false}) { + var lower = preserveHeaderCase ? name : name.toLowerCase(); + + if (_data.containsKey(lower)) { + if (value is Iterable) { + _data[lower].addAll(value.map((x) => x.toString()).toList()); + } else { + _data[lower].add(value.toString()); + } + } else { + if (value is Iterable) { + _data[lower] = value.map((x) => x.toString()).toList(); + } else { + _data[lower] = [value.toString()]; + } + } + } + + @override + void clear() { + _data.clear(); + } + + @override + void forEach(void Function(String name, List values) f) { + _data.forEach(f); + } + + @override + void noFolding(String name) { + _noFolding.add(name.toLowerCase()); + } + + @override + void remove(String name, Object value) { + var lower = name.toLowerCase(); + + if (_data.containsKey(lower)) { + if (value is Iterable) { + for (var x in value) { + _data[lower].remove(x.toString()); + } + } else { + _data[lower].remove(value.toString()); + } + } + } + + @override + void removeAll(String name) { + _data.remove(name.toLowerCase()); + } + + @override + void set(String name, Object value, {bool preserveHeaderCase = false}) { + var lower = preserveHeaderCase ? name : name.toLowerCase(); + _data.remove(lower); + + if (value is Iterable) { + _data[lower] = value.map((x) => x.toString()).toList(); + } else { + _data[lower] = [value.toString()]; + } + } + + @override + String value(String name) => _data[name.toLowerCase()]?.join(','); + + @override + String toString() { + var b = StringBuffer(); + _data.forEach((k, v) { + b.write('$k: '); + b.write(v.join(',')); + b.writeln(); + }); + return b.toString(); + } +} diff --git a/packages/mock_request/lib/src/lockable_headers.dart b/packages/mock_request/lib/src/lockable_headers.dart new file mode 100644 index 00000000..59b12e3b --- /dev/null +++ b/packages/mock_request/lib/src/lockable_headers.dart @@ -0,0 +1,67 @@ +import 'headers.dart'; + +/// Headers that can be locked to editing, i.e. after a request body has been written. +class LockableMockHttpHeaders extends MockHttpHeaders { + bool _locked = false; + + StateError _stateError() => + StateError('Cannot modify headers after they have been write-locked.'); + + void lock() { + _locked = true; + } + + @override + void add(String name, Object value, {bool preserveHeaderCase = false}) { + if (_locked) { + throw _stateError(); + } else { + super.add(name, value, preserveHeaderCase: preserveHeaderCase); + } + } + + @override + void clear() { + if (_locked) { + throw _stateError(); + } else { + super.clear(); + } + } + + @override + void noFolding(String name) { + if (_locked) { + throw _stateError(); + } else { + super.noFolding(name); + } + } + + @override + void remove(String name, Object value) { + if (_locked) { + throw _stateError(); + } else { + super.remove(name, value); + } + } + + @override + void removeAll(String name) { + if (_locked) { + throw _stateError(); + } else { + super.removeAll(name); + } + } + + @override + void set(String name, Object value, {bool preserveHeaderCase = false}) { + if (_locked) { + throw _stateError(); + } else { + super.set(name, value, preserveHeaderCase: preserveHeaderCase); + } + } +} diff --git a/packages/mock_request/lib/src/request.dart b/packages/mock_request/lib/src/request.dart new file mode 100644 index 00000000..5b976826 --- /dev/null +++ b/packages/mock_request/lib/src/request.dart @@ -0,0 +1,317 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:charcode/ascii.dart'; +import 'connection_info.dart'; +import 'lockable_headers.dart'; +import 'response.dart'; +import 'session.dart'; + +class MockHttpRequest + implements HttpRequest, StreamSink>, StringSink { + int _contentLength = 0; + BytesBuilder _buf; + final Completer _done = Completer(); + final LockableMockHttpHeaders _headers = LockableMockHttpHeaders(); + Uri _requestedUri; + MockHttpSession _session; + final StreamController _stream = StreamController(); + + @override + final List cookies = []; + + @override + HttpConnectionInfo connectionInfo = + MockHttpConnectionInfo(remoteAddress: InternetAddress.loopbackIPv4); + + @override + MockHttpResponse response = MockHttpResponse(); + + @override + HttpSession get session => _session; + + @override + final String method; + + @override + final Uri uri; + + @override + bool persistentConnection = true; + + /// [copyBuffer] corresponds to `copy` on the [BytesBuilder] constructor. + MockHttpRequest(this.method, this.uri, + {bool copyBuffer = true, + String protocolVersion, + String sessionId, + this.certificate, + this.persistentConnection}) { + _buf = BytesBuilder(copy: copyBuffer != false); + _session = MockHttpSession(id: sessionId ?? 'mock-http-session'); + this.protocolVersion = + protocolVersion?.isNotEmpty == true ? protocolVersion : '1.1'; + } + + @override + int get contentLength => _contentLength; + + @override + HttpHeaders get headers => _headers; + + @override + Uri get requestedUri { + if (_requestedUri != null) { + return _requestedUri; + } else { + return _requestedUri = Uri( + scheme: 'http', + host: 'example.com', + path: uri.path, + query: uri.query, + ); + } + } + + set requestedUri(Uri value) { + _requestedUri = value; + } + + @override + String protocolVersion; + + @override + X509Certificate certificate; + + @override + void add(List data) { + if (_done.isCompleted) { + throw StateError('Cannot add to closed MockHttpRequest.'); + } else { + _headers.lock(); + _contentLength += data.length; + _buf.add(data); + } + } + + @override + void addError(error, [StackTrace stackTrace]) { + if (_done.isCompleted) { + throw StateError('Cannot add to closed MockHttpRequest.'); + } else { + _stream.addError(error, stackTrace); + } + } + + @override + Future addStream(Stream> stream) { + var c = Completer(); + stream.listen(add, onError: addError, onDone: c.complete); + return c.future; + } + + @override + Future close() async { + await flush(); + _headers.lock(); + scheduleMicrotask(_stream.close); + _done.complete(); + return await _done.future; + } + + @override + Future get done => _done.future; + + // @override + Future flush() async { + _contentLength += _buf.length; + _stream.add(_buf.takeBytes()); + } + + @override + void write(Object obj) { + obj?.toString()?.codeUnits?.forEach(writeCharCode); + } + + @override + void writeAll(Iterable objects, [String separator = '']) { + write(objects.join(separator ?? '')); + } + + @override + void writeCharCode(int charCode) { + add([charCode]); + } + + @override + void writeln([Object obj = '']) { + write(obj ?? ''); + add([$cr, $lf]); + } + + @override + Future any(bool Function(Uint8List element) test) { + return _stream.stream.any((List e) { + return test(Uint8List.fromList(e)); + }); + } + + @override + Stream asBroadcastStream({ + void Function(StreamSubscription subscription) onListen, + void Function(StreamSubscription subscription) onCancel, + }) { + return _stream.stream + .asBroadcastStream(onListen: onListen, onCancel: onCancel); + } + + @override + Stream asyncExpand(Stream Function(Uint8List event) convert) => + _stream.stream.asyncExpand(convert); + + @override + Stream asyncMap(FutureOr Function(Uint8List event) convert) => + _stream.stream.asyncMap(convert); + + @override + Future contains(Object needle) => _stream.stream.contains(needle); + + @override + Stream distinct( + [bool Function(Uint8List previous, Uint8List next) equals]) => + _stream.stream.distinct(equals); + + @override + Future drain([E futureValue]) => _stream.stream.drain(futureValue); + + @override + Future elementAt(int index) => _stream.stream.elementAt(index); + + @override + Future every(bool Function(Uint8List element) test) => + _stream.stream.every(test); + + @override + Stream expand(Iterable Function(Uint8List value) convert) => + _stream.stream.expand(convert); + + @override + Future get first => _stream.stream.first; + + @override + Future firstWhere(bool Function(Uint8List element) test, + {List Function() orElse}) => + _stream.stream + .firstWhere(test, orElse: () => Uint8List.fromList(orElse())); + + @override + Future fold( + S initialValue, S Function(S previous, Uint8List element) combine) => + _stream.stream.fold(initialValue, combine); + + @override + Future forEach(void Function(Uint8List element) action) => + _stream.stream.forEach(action); + + @override + Stream handleError(Function onError, + {bool Function(Object) test}) => + _stream.stream.handleError(onError, test: test); + + @override + bool get isBroadcast => _stream.stream.isBroadcast; + + @override + Future get isEmpty => _stream.stream.isEmpty; + + @override + Future join([String separator = '']) => + _stream.stream.join(separator ?? ''); + + @override + Future get last => _stream.stream.last; + + @override + Future lastWhere(bool Function(Uint8List element) test, + {List Function() orElse}) => + _stream.stream + .lastWhere(test, orElse: () => Uint8List.fromList(orElse())); + + @override + Future get length => _stream.stream.length; + + @override + StreamSubscription listen( + void Function(Uint8List event) onData, { + Function onError, + void Function() onDone, + bool cancelOnError, + }) { + return _stream.stream.listen( + onData, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError == true, + ); + } + + @override + Stream map(S Function(Uint8List event) convert) => + _stream.stream.map(convert); + + @override + Future pipe(StreamConsumer> streamConsumer) => + _stream.stream.cast>().pipe(streamConsumer); + + @override + Future reduce( + List Function(Uint8List previous, Uint8List element) combine) { + return _stream.stream.reduce((Uint8List previous, Uint8List element) { + return Uint8List.fromList(combine(previous, element)); + }); + } + + @override + Future get single => _stream.stream.single; + + @override + Future singleWhere(bool Function(Uint8List element) test, + {List Function() orElse}) => + _stream.stream + .singleWhere(test, orElse: () => Uint8List.fromList(orElse())); + + @override + Stream skip(int count) => _stream.stream.skip(count); + + @override + Stream skipWhile(bool Function(Uint8List element) test) => + _stream.stream.skipWhile(test); + + @override + Stream take(int count) => _stream.stream.take(count); + + @override + Stream takeWhile(bool Function(Uint8List element) test) => + _stream.stream.takeWhile(test); + + @override + Stream timeout(Duration timeLimit, + {void Function(EventSink sink) onTimeout}) => + _stream.stream.timeout(timeLimit, onTimeout: onTimeout); + + @override + Future> toList() => _stream.stream.toList(); + + @override + Future> toSet() => _stream.stream.toSet(); + + @override + Stream transform(StreamTransformer, S> streamTransformer) => + _stream.stream.cast>().transform(streamTransformer); + + @override + Stream where(bool Function(Uint8List event) test) => + _stream.stream.where(test); + + @override + Stream cast() => Stream.castFrom, R>(this); +} diff --git a/packages/mock_request/lib/src/response.dart b/packages/mock_request/lib/src/response.dart new file mode 100644 index 00000000..bfc12d0f --- /dev/null +++ b/packages/mock_request/lib/src/response.dart @@ -0,0 +1,150 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:charcode/ascii.dart'; +import 'connection_info.dart'; +import 'lockable_headers.dart'; + +class MockHttpResponse extends Stream> implements HttpResponse { + BytesBuilder _buf = BytesBuilder(); + bool _bufferOutput = true; + final Completer _done = Completer(); + final LockableMockHttpHeaders _headers = LockableMockHttpHeaders(); + final StreamController> _stream = StreamController>(); + + @override + final List cookies = []; + + @override + HttpConnectionInfo connectionInfo = + MockHttpConnectionInfo(remoteAddress: InternetAddress.anyIPv4); + + /// [copyBuffer] corresponds to `copy` on the [BytesBuilder] constructor. + MockHttpResponse( + {bool copyBuffer = true, + this.statusCode, + this.reasonPhrase, + this.contentLength, + this.deadline, + this.encoding, + this.persistentConnection, + bool bufferOutput}) { + _buf = BytesBuilder(copy: copyBuffer != false); + _bufferOutput = bufferOutput != false; + statusCode ??= 200; + } + + @override + bool get bufferOutput => _bufferOutput; + + @override + set bufferOutput(bool value) {} + + @override + int contentLength; + + @override + Duration deadline; + + @override + bool persistentConnection; + + @override + String reasonPhrase; + + @override + int statusCode; + + @override + Encoding encoding; + + @override + HttpHeaders get headers => _headers; + + @override + Future get done => _done.future; + + @override + void add(List data) { + if (_done.isCompleted) { + throw StateError('Cannot add to closed MockHttpResponse.'); + } else { + _headers.lock(); + if (_bufferOutput == true) { + _buf.add(data); + } else { + _stream.add(data); + } + } + } + + @override + void addError(error, [StackTrace stackTrace]) { + if (_done.isCompleted) { + throw StateError('Cannot add to closed MockHttpResponse.'); + } else { + _stream.addError(error, stackTrace); + } + } + + @override + Future addStream(Stream> stream) { + var c = Completer(); + stream.listen(add, onError: addError, onDone: c.complete); + return c.future; + } + + @override + Future close() async { + _headers.lock(); + await flush(); + scheduleMicrotask(_stream.close); + _done.complete(); + //return await _done.future; + } + + @override + Future detachSocket({bool writeHeaders = true}) { + throw UnsupportedError('MockHttpResponses have no socket to detach.'); + } + + @override + Future flush() async { + _stream.add(_buf.takeBytes()); + } + + @override + Future redirect(Uri location, + {int status = HttpStatus.movedTemporarily}) async { + statusCode = status ?? HttpStatus.movedTemporarily; + } + + @override + void write(Object obj) { + obj?.toString()?.codeUnits?.forEach(writeCharCode); + } + + @override + void writeAll(Iterable objects, [String separator = '']) { + write(objects.join(separator ?? '')); + } + + @override + void writeCharCode(int charCode) { + add([charCode]); + } + + @override + void writeln([Object obj = '']) { + write(obj ?? ''); + add([$cr, $lf]); + } + + @override + StreamSubscription> listen(void Function(List event) onData, + {Function onError, void Function() onDone, bool cancelOnError}) => + _stream.stream.listen(onData, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError == true); +} diff --git a/packages/mock_request/lib/src/session.dart b/packages/mock_request/lib/src/session.dart new file mode 100644 index 00000000..387341ee --- /dev/null +++ b/packages/mock_request/lib/src/session.dart @@ -0,0 +1,74 @@ +import 'dart:collection'; +import 'dart:io'; + +class MockHttpSession extends MapBase implements HttpSession { + final Map _data = {}; + + @override + String id; + + MockHttpSession({this.id}); + + @override + int get length => _data.length; + + @override + dynamic operator [](Object key) => _data[key]; + + @override + void operator []=(key, value) { + _data[key] = value; + } + + @override + void addAll(Map other) => _data.addAll(other); + + @override + void clear() { + _data.clear(); + } + + @override + bool containsKey(Object key) => _data.containsKey(key); + + @override + bool containsValue(Object value) => _data.containsValue(value); + + @override + void destroy() { + print('destroy() was called on a MockHttpSession, which does nothing.'); + } + + @override + void forEach(void Function(dynamic, dynamic) f) { + _data.forEach(f); + } + + @override + bool get isEmpty => _data.isEmpty; + + @override + bool get isNew => true; + + @override + bool get isNotEmpty => _data.isNotEmpty; + + @override + Iterable get keys => _data.keys; + + @override + dynamic putIfAbsent(key, dynamic Function() ifAbsent) => + _data.putIfAbsent(key, ifAbsent); + + @override + dynamic remove(Object key) => _data.remove(key); + + @override + Iterable get values => _data.values; + + @override + set onTimeout(void Function() callback) { + print( + 'An onTimeout callback was set on a MockHttpSession, which will do nothing.'); + } +} diff --git a/packages/mock_request/pubspec.yaml b/packages/mock_request/pubspec.yaml new file mode 100644 index 00000000..4be6ff57 --- /dev/null +++ b/packages/mock_request/pubspec.yaml @@ -0,0 +1,13 @@ +name: mock_request +version: 2.0.0 +description: Manufacture dart:io HttpRequests, HttpResponses, HttpHeaders, etc. +author: Tobe O +homepage: https://github.com/thosakwe/mock_request +environment: + sdk: ">=2.0.0 <3.0.0" +dependencies: + charcode: ">=1.0.0 <2.0.0" +dev_dependencies: + angel_framework: ^2.1.0 + http: ^0.12.0 + test: ^1.16.8 diff --git a/packages/mock_request/test/all_test.dart b/packages/mock_request/test/all_test.dart new file mode 100644 index 00000000..97acd39b --- /dev/null +++ b/packages/mock_request/test/all_test.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_framework/http.dart'; +import 'package:mock_request/mock_request.dart'; +import 'package:test/test.dart'; + +void main() { + var uri = Uri.parse('http://localhost:3000'); + var app = Angel() + ..get('/foo', (req, res) => 'Hello, world!') + ..post('/body', + (req, res) => req.parseBody().then((_) => req.bodyAsMap.length)) + ..get('/session', (req, res) async { + req.session['foo'] = 'bar'; + }) + ..get('/conn', (RequestContext req, res) { + return res.serialize(req.ip == InternetAddress.loopbackIPv4.address); + }); + + var http = AngelHttp(app); + + test('receive a response', () async { + var rq = MockHttpRequest('GET', uri.resolve('/foo')); + await rq.close(); + await http.handleRequest(rq); + var rs = rq.response; + expect(rs.statusCode, equals(200)); + expect(await rs.transform(utf8.decoder).join(), + equals(json.encode('Hello, world!'))); + }); + + test('send a body', () async { + var rq = MockHttpRequest('POST', uri.resolve('/body')); + rq + ..headers.set(HttpHeaders.contentTypeHeader, ContentType.json.mimeType) + ..write(json.encode({'foo': 'bar', 'bar': 'baz', 'baz': 'quux'})); + await rq.close(); + await http.handleRequest(rq); + var rs = rq.response; + expect(rs.statusCode, equals(200)); + expect(await rs.transform(utf8.decoder).join(), equals(json.encode(3))); + }); + + test('session', () async { + var rq = MockHttpRequest('GET', uri.resolve('/session')); + await rq.close(); + await http.handleRequest(rq); + expect(rq.session.keys, contains('foo')); + expect(rq.session['foo'], equals('bar')); + }); + + test('connection info', () async { + var rq = MockHttpRequest('GET', uri.resolve('/conn')); + await rq.close(); + await http.handleRequest(rq); + var rs = rq.response; + expect(await rs.transform(utf8.decoder).join(), equals(json.encode(true))); + }); + + test('requested uri', () { + var rq = MockHttpRequest('GET', uri.resolve('/mock')); + expect(rq.uri.path, '/mock'); + expect(rq.requestedUri.toString(), 'http://example.com/mock'); + }); +} From 27ff8d935220c4437981996b497fd237aaf65fe7 Mon Sep 17 00:00:00 2001 From: thomashii Date: Fri, 19 Mar 2021 07:15:04 +0800 Subject: [PATCH 010/171] Fixed merge_map package version --- packages/merge_map/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/merge_map/pubspec.yaml b/packages/merge_map/pubspec.yaml index c23b39fb..6e430761 100644 --- a/packages/merge_map/pubspec.yaml +++ b/packages/merge_map/pubspec.yaml @@ -1,6 +1,6 @@ name: merge_map description: Combine multiple Maps into one. Equivalent to Object.assign in JS. -version: 3.0.0 +version: 2.0.0 homepage: https://github.com/thosakwe/merge_map author: Tobe O environment: From 554fbebe03781e221a6ef14d4cf1e5c2a6a38ef3 Mon Sep 17 00:00:00 2001 From: thomashii Date: Sat, 20 Mar 2021 10:33:34 +0800 Subject: [PATCH 011/171] Added mock_request and updated to 2.0.0 --- packages/mock_request/example/main.dart | 3 +- .../mock_request/lib/src/connection_info.dart | 5 +- packages/mock_request/lib/src/headers.dart | 56 +++++++-------- packages/mock_request/lib/src/request.dart | 70 ++++++++++--------- packages/mock_request/lib/src/response.dart | 30 ++++---- packages/mock_request/lib/src/session.dart | 10 +-- packages/mock_request/pubspec.yaml | 8 +-- packages/mock_request/test/all_test.dart | 17 +++-- 8 files changed, 107 insertions(+), 92 deletions(-) diff --git a/packages/mock_request/example/main.dart b/packages/mock_request/example/main.dart index 152ab0ed..cb41b329 100644 --- a/packages/mock_request/example/main.dart +++ b/packages/mock_request/example/main.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:mock_request/mock_request.dart'; Future main() async { - var rq = MockHttpRequest('GET', Uri.parse('/foo')); + var rq = + MockHttpRequest('GET', Uri.parse('/foo'), persistentConnection: false); await rq.close(); } diff --git a/packages/mock_request/lib/src/connection_info.dart b/packages/mock_request/lib/src/connection_info.dart index ba7abe88..ed76589f 100644 --- a/packages/mock_request/lib/src/connection_info.dart +++ b/packages/mock_request/lib/src/connection_info.dart @@ -6,5 +6,8 @@ class MockHttpConnectionInfo implements HttpConnectionInfo { @override final int localPort, remotePort; - MockHttpConnectionInfo({this.remoteAddress, this.localPort, this.remotePort}); + MockHttpConnectionInfo( + {required this.remoteAddress, + this.localPort = 8080, + this.remotePort = 80}); } diff --git a/packages/mock_request/lib/src/headers.dart b/packages/mock_request/lib/src/headers.dart index 913d1f1d..241cadf5 100644 --- a/packages/mock_request/lib/src/headers.dart +++ b/packages/mock_request/lib/src/headers.dart @@ -3,71 +3,71 @@ import 'dart:io'; class MockHttpHeaders extends HttpHeaders { final Map> _data = {}; final List _noFolding = []; - Uri _host; + Uri? _host; List get doNotFold => List.unmodifiable(_noFolding); @override ContentType get contentType { if (_data.containsKey(HttpHeaders.contentTypeHeader)) { - return ContentType.parse(_data[HttpHeaders.contentTypeHeader].join(',')); + return ContentType.parse(_data[HttpHeaders.contentTypeHeader]!.join(',')); } else { - return null; + return ContentType.html; } } @override - set contentType(ContentType value) => - set(HttpHeaders.contentTypeHeader, value.value); + set contentType(ContentType? value) => + set(HttpHeaders.contentTypeHeader, value?.value ?? ContentType.html); @override DateTime get date => _data.containsKey(HttpHeaders.dateHeader) - ? HttpDate.parse(_data[HttpHeaders.dateHeader].join(',')) - : null; + ? HttpDate.parse(_data[HttpHeaders.dateHeader]!.join(',')) + : DateTime.now(); @override - set date(DateTime value) => - set(HttpHeaders.dateHeader, HttpDate.format(value)); + set date(DateTime? value) => + set(HttpHeaders.dateHeader, HttpDate.format(value ?? DateTime.now())); @override DateTime get expires => _data.containsKey(HttpHeaders.expiresHeader) - ? HttpDate.parse(_data[HttpHeaders.expiresHeader].join(',')) - : null; + ? HttpDate.parse(_data[HttpHeaders.expiresHeader]!.join(',')) + : DateTime.now(); @override - set expires(DateTime value) => - set(HttpHeaders.expiresHeader, HttpDate.format(value)); + set expires(DateTime? value) => + set(HttpHeaders.expiresHeader, HttpDate.format(value ?? DateTime.now())); @override DateTime get ifModifiedSince => _data.containsKey(HttpHeaders.ifModifiedSinceHeader) - ? HttpDate.parse(_data[HttpHeaders.ifModifiedSinceHeader].join(',')) - : null; + ? HttpDate.parse(_data[HttpHeaders.ifModifiedSinceHeader]!.join(',')) + : DateTime.now(); @override - set ifModifiedSince(DateTime value) => - set(HttpHeaders.ifModifiedSinceHeader, HttpDate.format(value)); + set ifModifiedSince(DateTime? value) => set(HttpHeaders.ifModifiedSinceHeader, + HttpDate.format(value ?? DateTime.now())); @override - String get host { + String? get host { if (_host != null) { - return _host.host; + return _host!.host; } else if (_data.containsKey(HttpHeaders.hostHeader)) { - _host = Uri.parse(_data[HttpHeaders.hostHeader].join(',')); - return _host.host; + _host = Uri.parse(_data[HttpHeaders.hostHeader]!.join(',')); + return _host!.host; } else { return null; } } @override - int get port { + int? get port { host; // Parse it return _host?.port; } @override - List operator [](String name) => _data[name.toLowerCase()]; + List? operator [](String name) => _data[name.toLowerCase()]; @override void add(String name, Object value, {bool preserveHeaderCase = false}) { @@ -75,9 +75,9 @@ class MockHttpHeaders extends HttpHeaders { if (_data.containsKey(lower)) { if (value is Iterable) { - _data[lower].addAll(value.map((x) => x.toString()).toList()); + _data[lower]!.addAll(value.map((x) => x.toString()).toList()); } else { - _data[lower].add(value.toString()); + _data[lower]!.add(value.toString()); } } else { if (value is Iterable) { @@ -110,10 +110,10 @@ class MockHttpHeaders extends HttpHeaders { if (_data.containsKey(lower)) { if (value is Iterable) { for (var x in value) { - _data[lower].remove(x.toString()); + _data[lower]!.remove(x.toString()); } } else { - _data[lower].remove(value.toString()); + _data[lower]!.remove(value.toString()); } } } @@ -136,7 +136,7 @@ class MockHttpHeaders extends HttpHeaders { } @override - String value(String name) => _data[name.toLowerCase()]?.join(','); + String? value(String name) => _data[name.toLowerCase()]?.join(','); @override String toString() { diff --git a/packages/mock_request/lib/src/request.dart b/packages/mock_request/lib/src/request.dart index 5b976826..69d83b05 100644 --- a/packages/mock_request/lib/src/request.dart +++ b/packages/mock_request/lib/src/request.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:charcode/ascii.dart'; @@ -10,11 +11,11 @@ import 'session.dart'; class MockHttpRequest implements HttpRequest, StreamSink>, StringSink { int _contentLength = 0; - BytesBuilder _buf; + late BytesBuilder _buf; final Completer _done = Completer(); final LockableMockHttpHeaders _headers = LockableMockHttpHeaders(); - Uri _requestedUri; - MockHttpSession _session; + Uri? _requestedUri; + late MockHttpSession _session; final StreamController _stream = StreamController(); @override @@ -25,7 +26,12 @@ class MockHttpRequest MockHttpConnectionInfo(remoteAddress: InternetAddress.loopbackIPv4); @override - MockHttpResponse response = MockHttpResponse(); + MockHttpResponse response = MockHttpResponse( + contentLength: 0, + encoding: utf8, + persistentConnection: false, + reasonPhrase: '', + statusCode: 200); @override HttpSession get session => _session; @@ -42,14 +48,14 @@ class MockHttpRequest /// [copyBuffer] corresponds to `copy` on the [BytesBuilder] constructor. MockHttpRequest(this.method, this.uri, {bool copyBuffer = true, - String protocolVersion, - String sessionId, + String? protocolVersion, + String? sessionId, this.certificate, - this.persistentConnection}) { + required this.persistentConnection}) { _buf = BytesBuilder(copy: copyBuffer != false); _session = MockHttpSession(id: sessionId ?? 'mock-http-session'); this.protocolVersion = - protocolVersion?.isNotEmpty == true ? protocolVersion : '1.1'; + protocolVersion?.isNotEmpty == true ? protocolVersion! : '1.1'; } @override @@ -61,7 +67,7 @@ class MockHttpRequest @override Uri get requestedUri { if (_requestedUri != null) { - return _requestedUri; + return _requestedUri!; } else { return _requestedUri = Uri( scheme: 'http', @@ -77,10 +83,10 @@ class MockHttpRequest } @override - String protocolVersion; + late String protocolVersion; @override - X509Certificate certificate; + X509Certificate? certificate; @override void add(List data) { @@ -94,7 +100,7 @@ class MockHttpRequest } @override - void addError(error, [StackTrace stackTrace]) { + void addError(error, [StackTrace? stackTrace]) { if (_done.isCompleted) { throw StateError('Cannot add to closed MockHttpRequest.'); } else { @@ -128,7 +134,7 @@ class MockHttpRequest } @override - void write(Object obj) { + void write(Object? obj) { obj?.toString()?.codeUnits?.forEach(writeCharCode); } @@ -143,7 +149,7 @@ class MockHttpRequest } @override - void writeln([Object obj = '']) { + void writeln([Object? obj = '']) { write(obj ?? ''); add([$cr, $lf]); } @@ -157,15 +163,15 @@ class MockHttpRequest @override Stream asBroadcastStream({ - void Function(StreamSubscription subscription) onListen, - void Function(StreamSubscription subscription) onCancel, + void Function(StreamSubscription subscription)? onListen, + void Function(StreamSubscription subscription)? onCancel, }) { return _stream.stream .asBroadcastStream(onListen: onListen, onCancel: onCancel); } @override - Stream asyncExpand(Stream Function(Uint8List event) convert) => + Stream asyncExpand(Stream? Function(Uint8List event) convert) => _stream.stream.asyncExpand(convert); @override @@ -173,15 +179,15 @@ class MockHttpRequest _stream.stream.asyncMap(convert); @override - Future contains(Object needle) => _stream.stream.contains(needle); + Future contains(Object? needle) => _stream.stream.contains(needle); @override Stream distinct( - [bool Function(Uint8List previous, Uint8List next) equals]) => + [bool Function(Uint8List previous, Uint8List next)? equals]) => _stream.stream.distinct(equals); @override - Future drain([E futureValue]) => _stream.stream.drain(futureValue); + Future drain([E? futureValue]) => _stream.stream.drain(futureValue); @override Future elementAt(int index) => _stream.stream.elementAt(index); @@ -199,9 +205,9 @@ class MockHttpRequest @override Future firstWhere(bool Function(Uint8List element) test, - {List Function() orElse}) => + {List Function()? orElse}) => _stream.stream - .firstWhere(test, orElse: () => Uint8List.fromList(orElse())); + .firstWhere(test, orElse: () => Uint8List.fromList(orElse!())); @override Future fold( @@ -214,7 +220,7 @@ class MockHttpRequest @override Stream handleError(Function onError, - {bool Function(Object) test}) => + {bool Function(Object?)? test}) => _stream.stream.handleError(onError, test: test); @override @@ -232,19 +238,19 @@ class MockHttpRequest @override Future lastWhere(bool Function(Uint8List element) test, - {List Function() orElse}) => + {List Function()? orElse}) => _stream.stream - .lastWhere(test, orElse: () => Uint8List.fromList(orElse())); + .lastWhere(test, orElse: () => Uint8List.fromList(orElse!())); @override Future get length => _stream.stream.length; @override StreamSubscription listen( - void Function(Uint8List event) onData, { - Function onError, - void Function() onDone, - bool cancelOnError, + void Function(Uint8List event)? onData, { + Function? onError, + void Function()? onDone, + bool? cancelOnError, }) { return _stream.stream.listen( onData, @@ -275,9 +281,9 @@ class MockHttpRequest @override Future singleWhere(bool Function(Uint8List element) test, - {List Function() orElse}) => + {List Function()? orElse}) => _stream.stream - .singleWhere(test, orElse: () => Uint8List.fromList(orElse())); + .singleWhere(test, orElse: () => Uint8List.fromList(orElse!())); @override Stream skip(int count) => _stream.stream.skip(count); @@ -295,7 +301,7 @@ class MockHttpRequest @override Stream timeout(Duration timeLimit, - {void Function(EventSink sink) onTimeout}) => + {void Function(EventSink sink)? onTimeout}) => _stream.stream.timeout(timeLimit, onTimeout: onTimeout); @override diff --git a/packages/mock_request/lib/src/response.dart b/packages/mock_request/lib/src/response.dart index bfc12d0f..2d344115 100644 --- a/packages/mock_request/lib/src/response.dart +++ b/packages/mock_request/lib/src/response.dart @@ -16,22 +16,22 @@ class MockHttpResponse extends Stream> implements HttpResponse { final List cookies = []; @override - HttpConnectionInfo connectionInfo = - MockHttpConnectionInfo(remoteAddress: InternetAddress.anyIPv4); + HttpConnectionInfo connectionInfo = MockHttpConnectionInfo( + remoteAddress: InternetAddress.anyIPv4); /// [copyBuffer] corresponds to `copy` on the [BytesBuilder] constructor. MockHttpResponse( {bool copyBuffer = true, - this.statusCode, - this.reasonPhrase, - this.contentLength, + required this.statusCode, + required this.reasonPhrase, + required this.contentLength, this.deadline, - this.encoding, - this.persistentConnection, - bool bufferOutput}) { + required this.encoding, + required this.persistentConnection, + bool? bufferOutput}) { _buf = BytesBuilder(copy: copyBuffer != false); _bufferOutput = bufferOutput != false; - statusCode ??= 200; + statusCode = 200; } @override @@ -44,7 +44,7 @@ class MockHttpResponse extends Stream> implements HttpResponse { int contentLength; @override - Duration deadline; + Duration? deadline; @override bool persistentConnection; @@ -79,7 +79,7 @@ class MockHttpResponse extends Stream> implements HttpResponse { } @override - void addError(error, [StackTrace stackTrace]) { + void addError(error, [StackTrace? stackTrace]) { if (_done.isCompleted) { throw StateError('Cannot add to closed MockHttpResponse.'); } else { @@ -120,7 +120,7 @@ class MockHttpResponse extends Stream> implements HttpResponse { } @override - void write(Object obj) { + void write(Object? obj) { obj?.toString()?.codeUnits?.forEach(writeCharCode); } @@ -135,14 +135,14 @@ class MockHttpResponse extends Stream> implements HttpResponse { } @override - void writeln([Object obj = '']) { + void writeln([Object? obj = '']) { write(obj ?? ''); add([$cr, $lf]); } @override - StreamSubscription> listen(void Function(List event) onData, - {Function onError, void Function() onDone, bool cancelOnError}) => + StreamSubscription> listen(void Function(List event)? onData, + {Function? onError, void Function()? onDone, bool? cancelOnError}) => _stream.stream.listen(onData, onError: onError, onDone: onDone, diff --git a/packages/mock_request/lib/src/session.dart b/packages/mock_request/lib/src/session.dart index 387341ee..3a0a452d 100644 --- a/packages/mock_request/lib/src/session.dart +++ b/packages/mock_request/lib/src/session.dart @@ -7,13 +7,13 @@ class MockHttpSession extends MapBase implements HttpSession { @override String id; - MockHttpSession({this.id}); + MockHttpSession({required this.id}); @override int get length => _data.length; @override - dynamic operator [](Object key) => _data[key]; + dynamic operator [](Object? key) => _data[key]; @override void operator []=(key, value) { @@ -29,10 +29,10 @@ class MockHttpSession extends MapBase implements HttpSession { } @override - bool containsKey(Object key) => _data.containsKey(key); + bool containsKey(Object? key) => _data.containsKey(key); @override - bool containsValue(Object value) => _data.containsValue(value); + bool containsValue(Object? value) => _data.containsValue(value); @override void destroy() { @@ -61,7 +61,7 @@ class MockHttpSession extends MapBase implements HttpSession { _data.putIfAbsent(key, ifAbsent); @override - dynamic remove(Object key) => _data.remove(key); + dynamic remove(Object? key) => _data.remove(key); @override Iterable get values => _data.values; diff --git a/packages/mock_request/pubspec.yaml b/packages/mock_request/pubspec.yaml index 4be6ff57..b0e8265a 100644 --- a/packages/mock_request/pubspec.yaml +++ b/packages/mock_request/pubspec.yaml @@ -4,10 +4,10 @@ description: Manufacture dart:io HttpRequests, HttpResponses, HttpHeaders, etc. author: Tobe O homepage: https://github.com/thosakwe/mock_request environment: - sdk: ">=2.0.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: - charcode: ">=1.0.0 <2.0.0" + charcode: ^1.2.0 dev_dependencies: - angel_framework: ^2.1.0 - http: ^0.12.0 + #angel_framework: ^2.1.0 + http: ^0.13.0 test: ^1.16.8 diff --git a/packages/mock_request/test/all_test.dart b/packages/mock_request/test/all_test.dart index 97acd39b..8fb38210 100644 --- a/packages/mock_request/test/all_test.dart +++ b/packages/mock_request/test/all_test.dart @@ -1,12 +1,15 @@ -import 'dart:convert'; -import 'dart:io'; -import 'package:angel_framework/angel_framework.dart'; -import 'package:angel_framework/http.dart'; -import 'package:mock_request/mock_request.dart'; -import 'package:test/test.dart'; +//import 'dart:convert'; +//import 'dart:io'; +//import 'package:angel_framework/angel_framework.dart'; +//import 'package:angel_framework/http.dart'; +//import 'package:mock_request/mock_request.dart'; +//import 'package:test/test.dart'; void main() { + /* var uri = Uri.parse('http://localhost:3000'); + + var app = Angel() ..get('/foo', (req, res) => 'Hello, world!') ..post('/body', @@ -18,6 +21,7 @@ void main() { return res.serialize(req.ip == InternetAddress.loopbackIPv4.address); }); + var http = AngelHttp(app); test('receive a response', () async { @@ -63,4 +67,5 @@ void main() { expect(rq.uri.path, '/mock'); expect(rq.requestedUri.toString(), 'http://example.com/mock'); }); +*/ } From 0fe0a5a56b60fb603b897ab8fffd9ba75cdbcf7e Mon Sep 17 00:00:00 2001 From: thomashii Date: Sat, 20 Mar 2021 10:42:43 +0800 Subject: [PATCH 012/171] Updated angel_framework to null safety --- packages/framework/lib/src/core/service.dart | 2 +- packages/framework/pubspec.yaml | 39 ++++++++++++-------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/packages/framework/lib/src/core/service.dart b/packages/framework/lib/src/core/service.dart index 7592c48d..fdde7586 100644 --- a/packages/framework/lib/src/core/service.dart +++ b/packages/framework/lib/src/core/service.dart @@ -3,7 +3,7 @@ library angel_framework.http.service; import 'dart:async'; import 'package:angel_http_exception/angel_http_exception.dart'; import 'package:merge_map/merge_map.dart'; -import 'package:quiver_hashcode/hashcode.dart'; +import 'package:quiver/core.dart'; import '../util.dart'; import 'anonymous_service.dart'; import 'hooked_service.dart' show HookedService; diff --git a/packages/framework/pubspec.yaml b/packages/framework/pubspec.yaml index 18d6acb0..562ede92 100644 --- a/packages/framework/pubspec.yaml +++ b/packages/framework/pubspec.yaml @@ -27,7 +27,7 @@ dependencies: url: https://github.com/dukefirehawk/angel.git ref: sdk-2.12.x_nnbd path: packages/route - charcode: ^1.0.0 + charcode: ^1.2.0 combinator: git: url: https://github.com/dukefirehawk/angel.git @@ -35,25 +35,34 @@ dependencies: path: packages/combinator file: ^6.1.0 http_parser: ^4.0.0 - http_server: ^0.9.0 + http_server: ^1.0.0 http2: ^2.0.0 logging: ^1.0.0 - matcher: ^0.12.0 - merge_map: ^1.0.0 - meta: ^1.0.0 - mime: ^0.9.0 - mock_request: ^1.0.0 - path: ^1.0.0 + matcher: ^0.12.10 + merge_map: + git: + url: https://github.com/dukefirehawk/angel.git + ref: sdk-2.12.x_nnbd + path: packages/merge_map + meta: ^1.3.0 + mime: ^1.0.0 + mock_request: + git: + url: https://github.com/dukefirehawk/angel.git + ref: sdk-2.12.x_nnbd + path: packages/mock_request + path: ^1.8.0 # pedantic: ^1.0.0 - quiver_hashcode: ^2.0.0 - recase: ^3.0.1 - stack_trace: ^1.0.0 - string_scanner: ^1.0.0 - tuple: ^1.0.3 + #quiver_hashcode: ^3.0.0+1 + quiver: ^3.0.0 + recase: ^4.0.0-nullsafety.0 + stack_trace: ^1.10.0 + string_scanner: ^1.1.0 + tuple: ^2.0.0 uuid: ^3.0.1 dev_dependencies: - http: ^0.13.0 + http: ^0.13.1 io: ^1.0.0 #pretty_logging: ^1.0.0 - test: ^1.15.7 + test: ^1.16.8 From fdf15320747e228c1ee86f587c0b4bb1d8322617 Mon Sep 17 00:00:00 2001 From: thomashii Date: Sat, 20 Mar 2021 16:11:18 +0800 Subject: [PATCH 013/171] Updated angel framework to NNBD --- CHANGELOG.md | 8 +- packages/auth/pubspec.yaml | 16 +- packages/framework/example/controller.dart | 6 +- packages/framework/example/handle_error.dart | 2 +- .../framework/example/http2/body_parsing.dart | 4 +- packages/framework/example/http2/main.dart | 2 +- .../framework/example/http2/server_push.dart | 4 +- packages/framework/example/json.dart | 2 +- packages/framework/example/main.dart | 6 +- packages/framework/example/view.dart | 4 +- .../lib/src/core/anonymous_service.dart | 46 +++--- .../framework/lib/src/core/controller.dart | 58 +++---- packages/framework/lib/src/core/driver.dart | 143 +++++++++--------- packages/framework/lib/src/core/env.dart | 2 +- .../lib/src/core/hooked_service.dart | 68 ++++----- .../lib/src/core/hostname_parser.dart | 17 +-- .../lib/src/core/hostname_router.dart | 16 +- .../framework/lib/src/core/injection.dart | 43 +++--- .../framework/lib/src/core/map_service.dart | 28 ++-- packages/framework/lib/src/core/metadata.dart | 24 +-- .../lib/src/core/request_context.dart | 132 ++++++++-------- .../lib/src/core/response_context.dart | 96 ++++++------ packages/framework/lib/src/core/routable.dart | 32 ++-- packages/framework/lib/src/core/server.dart | 58 +++---- packages/framework/lib/src/core/service.dart | 136 +++++++++-------- .../framework/lib/src/http/angel_http.dart | 36 +++-- .../lib/src/http/http_request_context.dart | 38 ++--- .../lib/src/http/http_response_context.dart | 40 ++--- .../framework/lib/src/http2/angel_http2.dart | 50 +++--- .../lib/src/http2/http2_request_context.dart | 43 +++--- .../lib/src/http2/http2_response_context.dart | 50 +++--- .../lib/src/safe_stream_controller.dart | 26 ++-- packages/framework/lib/src/util.dart | 10 +- packages/framework/pubspec.yaml | 3 +- packages/framework/test/404_hole_test.dart | 6 +- packages/framework/test/accepts_test.dart | 8 +- .../test/anonymous_service_test.dart | 2 +- packages/framework/test/body_test.dart | 17 ++- packages/framework/test/common.dart | 4 +- packages/framework/test/controller_test.dart | 36 ++--- packages/framework/test/detach_test.dart | 2 +- packages/framework/test/di_test.dart | 48 +++--- .../framework/test/encoders_buffer_test.dart | 4 +- packages/framework/test/general_test.dart | 12 +- packages/framework/test/hm.dart | 2 +- packages/framework/test/hooked_test.dart | 53 +++---- .../framework/test/http2/adapter_test.dart | 21 +-- .../framework/test/http2/http2_client.dart | 4 +- .../framework/test/parameter_meta_test.dart | 2 +- packages/framework/test/primitives_test.dart | 4 +- .../framework/test/req_shutdown_test.dart | 8 +- packages/framework/test/routing_test.dart | 90 +++++------ packages/framework/test/serialize_test.dart | 14 +- packages/framework/test/server_test.dart | 14 +- packages/framework/test/service_map_test.dart | 16 +- packages/framework/test/services_test.dart | 50 +++--- packages/framework/test/streaming_test.dart | 4 +- .../framework/test/view_generator_test.dart | 2 +- 58 files changed, 860 insertions(+), 812 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d51e4858..a220dd91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,11 @@ * Updated angel_route to 5.0.0 * Updated angel_model to 3.0.0 * Updated angel_container to 3.0.0 -* Updated angel_framework to 4.0.0 - - merge_map - - mock_request +* Added merge_map and updated to 2.0.0 +* Added mock_request and updated to 2.0.0 +* Updated angel_framework to 4.0.0 (Revisit TODO) +* Updated angel_auth to 4.0.0 (todo) +* Updated angel_configuration to 4.0.0 (todo) # 3.0.0 (Non NNBD) * Changed Dart SDK requirements for all packages to ">=2.10.0 <3.0.0" diff --git a/packages/auth/pubspec.yaml b/packages/auth/pubspec.yaml index ff3b8003..fffbf774 100644 --- a/packages/auth/pubspec.yaml +++ b/packages/auth/pubspec.yaml @@ -10,17 +10,17 @@ 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 - charcode: ^1.0.0 - collection: ^1.0.0 + charcode: ^1.2.0 + collection: ^1.15.0 crypto: ^3.0.0 http_parser: ^4.0.0 - meta: ^1.0.0 - quiver_hashcode: ^2.0.0 + meta: ^1.3.0 + quiver_hashcode: ^3.0.0+1 dev_dependencies: - http: ^0.13.0 + http: ^0.13.1 io: ^1.0.0 logging: ^1.0.0 - pedantic: ^1.0.0 - test: ^1.15.7 + pedantic: ^1.11.0 + test: ^1.16.8 diff --git a/packages/framework/example/controller.dart b/packages/framework/example/controller.dart index 1e0da928..872f8051 100644 --- a/packages/framework/example/controller.dart +++ b/packages/framework/example/controller.dart @@ -16,7 +16,7 @@ main() async { // Simple fallback to throw a 404 on unknown paths. app.fallback((req, res) { throw AngelHttpException.notFound( - message: 'Unknown path: "${req.uri.path}"', + message: 'Unknown path: "${req.uri!.path}"', ); }); @@ -40,7 +40,7 @@ class ArtistsController extends Controller { form(RequestContext req) async { // Deserialize the body into an artist. var artist = await req.deserializeBody((m) { - return Artist(name: m['name'] as String ?? '(unknown name)'); + return Artist(name: m!['name'] as String? ?? '(unknown name)'); }); // Return it (it will be serialized to JSON). @@ -49,7 +49,7 @@ class ArtistsController extends Controller { } class Artist { - final String name; + final String? name; Artist({this.name}); diff --git a/packages/framework/example/handle_error.dart b/packages/framework/example/handle_error.dart index e159a3ce..e964092a 100644 --- a/packages/framework/example/handle_error.dart +++ b/packages/framework/example/handle_error.dart @@ -19,7 +19,7 @@ main() async { (req, res) => Future.error('Throwing just because I feel like!')); var http = AngelHttp(app); - var server = await http.startServer('127.0.0.1', 3000); + HttpServer server = await http.startServer('127.0.0.1', 3000); var url = 'http://${server.address.address}:${server.port}'; print('Listening at $url'); } diff --git a/packages/framework/example/http2/body_parsing.dart b/packages/framework/example/http2/body_parsing.dart index a0c27a9c..72718d5c 100644 --- a/packages/framework/example/http2/body_parsing.dart +++ b/packages/framework/example/http2/body_parsing.dart @@ -29,7 +29,7 @@ main() async { try { ctx.setAlpnProtocols(['h2'], true); } catch (e, st) { - app.logger.severe( + app.logger!.severe( 'Cannot set ALPN protocol on server to `h2`. The server will only serve HTTP/1.x.', e, st); @@ -41,6 +41,6 @@ main() async { // HTTP/1.x requests will fallback to `AngelHttp` http2.onHttp1.listen(http1.handleRequest); - var server = await http2.startServer('127.0.0.1', 3000); + SecureServerSocket server = await http2.startServer('127.0.0.1', 3000); print('Listening at https://${server.address.address}:${server.port}'); } diff --git a/packages/framework/example/http2/main.dart b/packages/framework/example/http2/main.dart index d8218ae2..11ed2f63 100644 --- a/packages/framework/example/http2/main.dart +++ b/packages/framework/example/http2/main.dart @@ -25,7 +25,7 @@ main() async { try { ctx.setAlpnProtocols(['h2'], true); } catch (e, st) { - app.logger.severe( + app.logger!.severe( 'Cannot set ALPN protocol on server to `h2`. The server will only serve HTTP/1.x.', e, st, diff --git a/packages/framework/example/http2/server_push.dart b/packages/framework/example/http2/server_push.dart index 41a1170f..9dcd1a66 100644 --- a/packages/framework/example/http2/server_push.dart +++ b/packages/framework/example/http2/server_push.dart @@ -45,7 +45,7 @@ main() async { try { ctx.setAlpnProtocols(['h2'], true); } catch (e, st) { - app.logger.severe( + app.logger!.severe( 'Cannot set ALPN protocol on server to `h2`. The server will only serve HTTP/1.x.', e, st); @@ -57,6 +57,6 @@ main() async { // HTTP/1.x requests will fallback to `AngelHttp` http2.onHttp1.listen(http1.handleRequest); - var server = await http2.startServer('127.0.0.1', 3000); + SecureServerSocket server = await http2.startServer('127.0.0.1', 3000); print('Listening at https://${server.address.address}:${server.port}'); } diff --git a/packages/framework/example/json.dart b/packages/framework/example/json.dart index f71a48dc..16ae5baf 100644 --- a/packages/framework/example/json.dart +++ b/packages/framework/example/json.dart @@ -48,6 +48,6 @@ serverMain(_) async { print(e.stackTrace); }; - var server = await http.startServer('127.0.0.1', 3000); + HttpServer server = await http.startServer('127.0.0.1', 3000); print('Listening at http://${server.address.address}:${server.port}'); } diff --git a/packages/framework/example/main.dart b/packages/framework/example/main.dart index ce99ce09..502687af 100644 --- a/packages/framework/example/main.dart +++ b/packages/framework/example/main.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:angel_container/mirrors.dart'; import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/http.dart'; @@ -42,12 +44,12 @@ main() async { // Simple fallback to throw a 404 on unknown paths. app.fallback((req, res) { throw AngelHttpException.notFound( - message: 'Unknown path: "${req.uri.path}"', + message: 'Unknown path: "${req.uri!.path}"', ); }); var http = AngelHttp(app); - var server = await http.startServer('127.0.0.1', 3000); + HttpServer server = await http.startServer('127.0.0.1', 3000); var url = 'http://${server.address.address}:${server.port}'; print('Listening at $url'); print('Visit these pages to see Angel in action:'); diff --git a/packages/framework/example/view.dart b/packages/framework/example/view.dart index c90fd3cd..809a6cf6 100644 --- a/packages/framework/example/view.dart +++ b/packages/framework/example/view.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:angel_container/mirrors.dart'; import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/http.dart'; @@ -12,7 +14,7 @@ main() async { app.get('/', (req, res) => res.render('index', {'foo': 'bar'})); var http = AngelHttp(app); - var server = await http.startServer('127.0.0.1', 3000); + HttpServer server = await http.startServer('127.0.0.1', 3000); var url = 'http://${server.address.address}:${server.port}'; print('Listening at $url'); } diff --git a/packages/framework/lib/src/core/anonymous_service.dart b/packages/framework/lib/src/core/anonymous_service.dart index f4d86f72..185d8a9a 100644 --- a/packages/framework/lib/src/core/anonymous_service.dart +++ b/packages/framework/lib/src/core/anonymous_service.dart @@ -7,19 +7,19 @@ import 'service.dart'; /// /// Well-suited for testing. class AnonymousService extends Service { - FutureOr> Function([Map]) _index; - FutureOr Function(Id, [Map]) _read, _remove; - FutureOr Function(Data, [Map]) _create; - FutureOr Function(Id, Data, [Map]) _modify, _update; + FutureOr>? Function([Map?])? _index; + FutureOr Function(Id?, [Map?])? _read, _remove; + FutureOr Function(Data, [Map?])? _create; + FutureOr Function(Id?, Data, [Map?])? _modify, _update; AnonymousService( - {FutureOr> index([Map params]), - FutureOr read(Id id, [Map params]), - FutureOr create(Data data, [Map params]), - FutureOr modify(Id id, Data data, [Map params]), - FutureOr update(Id id, Data data, [Map params]), - FutureOr remove(Id id, [Map params]), - FutureOr Function(RequestContext, ResponseContext) readData}) + {FutureOr>? index([Map? params])?, + FutureOr read(Id? id, [Map? params])?, + FutureOr create(Data data, [Map? params])?, + FutureOr modify(Id? id, Data data, [Map? params])?, + FutureOr update(Id? id, Data data, [Map? params])?, + FutureOr remove(Id? id, [Map? params])?, + FutureOr Function(RequestContext, ResponseContext?)? readData}) : super(readData: readData) { _index = index; _read = read; @@ -30,30 +30,30 @@ class AnonymousService extends Service { } @override - index([Map params]) => - Future.sync(() => _index != null ? _index(params) : super.index(params)); + index([Map? params]) => + Future.sync(() => _index != null ? _index!(params)! : super.index(params)); @override - read(Id id, [Map params]) => Future.sync( - () => _read != null ? _read(id, params) : super.read(id, params)); + read(Id? id, [Map? params]) => Future.sync( + () => _read != null ? _read!(id, params) : super.read(id, params)); @override - create(Data data, [Map params]) => Future.sync(() => - _create != null ? _create(data, params) : super.create(data, params)); + create(Data data, [Map? params]) => Future.sync(() => + _create != null ? _create!(data, params) : super.create(data, params)); @override - modify(Id id, Data data, [Map params]) => + modify(Id? id, Data data, [Map? params]) => Future.sync(() => _modify != null - ? _modify(id, data, params) + ? _modify!(id, data, params) : super.modify(id, data, params)); @override - update(Id id, Data data, [Map params]) => + update(Id? id, Data data, [Map? params]) => Future.sync(() => _update != null - ? _update(id, data, params) + ? _update!(id, data, params) : super.update(id, data, params)); @override - remove(Id id, [Map params]) => Future.sync( - () => _remove != null ? _remove(id, params) : super.remove(id, params)); + remove(Id? id, [Map? params]) => Future.sync( + () => _remove != null ? _remove!(id, params) : super.remove(id, params)); } diff --git a/packages/framework/lib/src/core/controller.dart b/packages/framework/lib/src/core/controller.dart index e531b12b..d9b3efb6 100644 --- a/packages/framework/lib/src/core/controller.dart +++ b/packages/framework/lib/src/core/controller.dart @@ -9,10 +9,10 @@ import '../core/core.dart'; /// Supports grouping routes with shared functionality. class Controller { - Angel _app; + Angel? _app; /// The [Angel] application powering this controller. - Angel get app => _app; + Angel? get app => _app; /// If `true` (default), this class will inject itself as a singleton into the [app]'s container when bootstrapped. final bool injectSingleton; @@ -21,12 +21,12 @@ class Controller { List middleware = []; /// A mapping of route paths to routes, produced from the [Expose] annotations on this class. - Map routeMappings = {}; + Map routeMappings = {}; - SymlinkRoute _mountPoint; + SymlinkRoute? _mountPoint; /// The route at which this controller is mounted on the server. - SymlinkRoute get mountPoint => _mountPoint; + SymlinkRoute? get mountPoint => _mountPoint; Controller({this.injectSingleton = true}); @@ -36,35 +36,35 @@ class Controller { _app = app; if (injectSingleton != false) { - if (!app.container.has(runtimeType)) { - _app.container.registerSingleton(this, as: runtimeType); + if (!app.container!.has(runtimeType)) { + _app!.container!.registerSingleton(this, as: runtimeType); } } - var name = await applyRoutes(app, app.container.reflector); + var name = await applyRoutes(app, app.container!.reflector); app.controllers[name] = this; return null; } /// Applies the routes from this [Controller] to some [router]. - Future applyRoutes( - Router router, Reflector reflector) async { + Future applyRoutes( + Router router, Reflector reflector) async { // Load global expose decl - var classMirror = reflector.reflectClass(this.runtimeType); - Expose exposeDecl = findExpose(reflector); + var classMirror = reflector.reflectClass(this.runtimeType)!; + Expose? exposeDecl = findExpose(reflector); if (exposeDecl == null) { throw Exception("All controllers must carry an @Expose() declaration."); } var routable = Routable(); - _mountPoint = router.mount(exposeDecl.path, routable); + _mountPoint = router.mount(exposeDecl.path!, routable); var typeMirror = reflector.reflectType(this.runtimeType); // Pre-reflect methods var instanceMirror = reflector.reflectInstance(this); final handlers = [] - ..addAll(exposeDecl.middleware) + ..addAll(exposeDecl.middleware!) ..addAll(middleware); final routeBuilder = _routeBuilder(reflector, instanceMirror, routable, handlers); @@ -72,12 +72,12 @@ class Controller { classMirror.declarations.forEach(routeBuilder); // Return the name. - return exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : typeMirror.name; + return exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : typeMirror!.name; } void Function(ReflectedDeclaration) _routeBuilder( Reflector reflector, - ReflectedInstance instanceMirror, + ReflectedInstance? instanceMirror, Routable routable, Iterable handlers) { return (ReflectedDeclaration decl) { @@ -89,13 +89,13 @@ class Controller { methodName != 'call' && methodName != 'equals' && methodName != '==') { - var exposeDecl = decl.function.annotations + var exposeDecl = decl.function!.annotations .map((m) => m.reflectee) - .firstWhere((r) => r is Expose, orElse: () => null) as Expose; + .firstWhere((r) => r is Expose, orElse: () => null) as Expose?; if (exposeDecl == null) { // If this has a @noExpose, return null. - if (decl.function.annotations.any((m) => m.reflectee is NoExpose)) { + if (decl.function!.annotations.any((m) => m.reflectee is NoExpose)) { return; } else { // Otherwise, create an @Expose. @@ -104,15 +104,15 @@ class Controller { } var reflectedMethod = - instanceMirror.getField(methodName).reflectee as Function; + instanceMirror!.getField(methodName).reflectee as Function?; var middleware = [] ..addAll(handlers) - ..addAll(exposeDecl.middleware); - String name = + ..addAll(exposeDecl.middleware!); + String? name = exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : methodName; // Check if normal - var method = decl.function; + var method = decl.function!; if (method.parameters.length == 2 && method.parameters[0].type.reflectedType == RequestContext && method.parameters[1].type.reflectedType == ResponseContext) { @@ -120,13 +120,13 @@ class Controller { routeMappings[name] = routable .addRoute(exposeDecl.method, exposeDecl.path, (RequestContext req, ResponseContext res) { - var result = reflectedMethod(req, res); + var result = reflectedMethod!(req, res); return result is RequestHandler ? result(req, res) : result; }, middleware: middleware); return; } - var injection = preInject(reflectedMethod, reflector); + var injection = preInject(reflectedMethod!, reflector); if (exposeDecl?.allowNull?.isNotEmpty == true) { injection.optional?.addAll(exposeDecl.allowNull); @@ -148,7 +148,7 @@ class Controller { var restPath = ReCase(rest.isEmpty ? 'index' : rest) .snakeCase .replaceAll(_rgxMultipleUnderscores, '_'); - httpMethod = methodMatch[1].toUpperCase(); + httpMethod = methodMatch[1]!.toUpperCase(); if (['index', 'by_id'].contains(restPath)) { parts.add('/'); @@ -215,12 +215,12 @@ class Controller { /// /// If [concreteOnly] is `false`, then if there is no actual /// [Expose], one will be automatically created. - Expose findExpose(Reflector reflector, {bool concreteOnly = false}) { + Expose? findExpose(Reflector reflector, {bool concreteOnly = false}) { var existing = reflector - .reflectClass(runtimeType) + .reflectClass(runtimeType)! .annotations .map((m) => m.reflectee) - .firstWhere((r) => r is Expose, orElse: () => null) as Expose; + .firstWhere((r) => r is Expose, orElse: () => null) as Expose?; return existing ?? (concreteOnly ? null diff --git a/packages/framework/lib/src/core/driver.dart b/packages/framework/lib/src/core/driver.dart index b15df8e5..f1cf5773 100644 --- a/packages/framework/lib/src/core/driver.dart +++ b/packages/framework/lib/src/core/driver.dart @@ -17,11 +17,11 @@ abstract class Driver< Server extends Stream, RequestContextType extends RequestContext, ResponseContextType extends ResponseContext> { - final Angel app; + final Angel? app; final bool useZone; bool _closed = false; - Server _server; - StreamSubscription _sub; + Server? _server; + StreamSubscription? _sub; /// The function used to bind this instance to a server.. final Future Function(dynamic, int) serverGenerator; @@ -32,25 +32,27 @@ abstract class Driver< Uri get uri; /// The native server running this instance. - Server get server => _server; + Server? get server => _server; Future generateServer(address, int port) => serverGenerator(address, port); /// Starts, and returns the server. - Future startServer([address, int port]) { + Future startServer([address, int? port]) { var host = address ?? '127.0.0.1'; return generateServer(host, port ?? 0).then((server) { _server = server; - return Future.wait(app.startupHooks.map(app.configure)).then((_) { - app.optimizeForProduction(); + return Future.wait(app!.startupHooks.map(app!.configure)).then((_) { + app!.optimizeForProduction(); _sub = server.listen((request) { var stream = createResponseStreamFromRawRequest(request); stream.listen((response) { - return handleRawRequest(request, response); + // TODO: To be revisited + handleRawRequest(request, response); + return; }); }); - return _server; + return _server!; }); }); } @@ -60,8 +62,9 @@ abstract class Driver< if (_closed) return Future.value(_server); _closed = true; _sub?.cancel(); - return app.close().then((_) => - Future.wait(app.shutdownHooks.map(app.configure)).then((_) => _server)); + return app!.close().then((_) => + Future.wait(app!.shutdownHooks.map(app!.configure)) + .then((_) => _server!)); } Future createRequestContext( @@ -69,9 +72,9 @@ abstract class Driver< Future createResponseContext( Request request, Response response, - [RequestContextType correspondingRequest]); + [RequestContextType? correspondingRequest]); - void setHeader(Response response, String key, String value); + void setHeader(Response response, String key, String? value); void setContentLength(Response response, int length); @@ -97,42 +100,42 @@ abstract class Driver< var path = req.path; if (path == '/') path = ''; - Tuple4, ParseResult, + Tuple4, ParseResult?, MiddlewarePipeline> resolveTuple() { - var r = app.optimizedRouter; + var r = app!.optimizedRouter; var resolved = - r.resolveAbsolute(path, method: req.method, strip: false); - var pipeline = MiddlewarePipeline(resolved); + r.resolveAbsolute(path, method: req.method!, strip: false); + var pipeline = MiddlewarePipeline(resolved); return Tuple4( pipeline.handlers, - resolved.fold>( - {}, (out, r) => out..addAll(r.allParams)), + resolved.fold>( + {}, (out, r) => out..addAll(r.allParams)), resolved.isEmpty ? null : resolved.first.parseResult, pipeline, ); } - var cacheKey = req.method + path; - var tuple = app.environment.isProduction - ? app.handlerCache.putIfAbsent(cacheKey, resolveTuple) + var cacheKey = req.method! + path!; + var tuple = app!.environment.isProduction + ? app!.handlerCache.putIfAbsent(cacheKey, resolveTuple) : resolveTuple(); - var line = tuple.item4 as MiddlewarePipeline; - var it = MiddlewarePipelineIterator(line); + var line = tuple.item4 as MiddlewarePipeline; + var it = MiddlewarePipelineIterator(line); req.params.addAll(tuple.item2); req.container - ..registerSingleton(req) + ?..registerSingleton(req) ..registerSingleton(res) ..registerSingleton(tuple.item4) - ..registerSingleton>(line) + ..registerSingleton>(line) ..registerSingleton(it) - ..registerSingleton>(it) - ..registerSingleton>(tuple.item3) - ..registerSingleton(tuple.item3); + ..registerSingleton>(it) + ..registerSingleton?>(tuple.item3) + ..registerSingleton(tuple.item3); - if (!app.environment.isProduction && app.logger != null) { - req.container.registerSingleton(Stopwatch()..start()); + if (!app!.environment.isProduction && app!.logger != null) { + req.container!.registerSingleton(Stopwatch()..start()); } return runPipeline(it, req, res, app) @@ -161,10 +164,10 @@ abstract class Driver< (ee, StackTrace st) { var e = ee as AngelHttpException; - if (app.logger != null) { + if (app!.logger != null) { var error = e.error ?? e; var trace = Trace.from(e.stackTrace ?? StackTrace.current).terse; - app.logger.severe(e.message ?? e.toString(), error, trace); + app!.logger!.severe(e.message ?? e.toString(), error, trace); } return handleAngelHttpException( @@ -173,16 +176,17 @@ abstract class Driver< } else { var zoneSpec = ZoneSpecification( print: (self, parent, zone, line) { - if (app.logger != null) { - app.logger.info(line); + if (app!.logger != null) { + app!.logger!.info(line); } else { parent.print(zone, line); } }, handleUncaughtError: (self, parent, zone, error, stackTrace) { - var trace = Trace.from(stackTrace ?? StackTrace.current).terse; + var trace = Trace.from(stackTrace).terse; - return Future(() { + // TODO: To be revisited + Future(() { AngelHttpException e; if (error is FormatException) { @@ -191,24 +195,22 @@ abstract class Driver< e = error; } else { e = AngelHttpException(error, - stackTrace: stackTrace, - message: - error?.toString() ?? '500 Internal Server Error'); + stackTrace: stackTrace, message: error.toString()); } - if (app.logger != null) { - app.logger.severe(e.message ?? e.toString(), error, trace); + if (app!.logger != null) { + app!.logger!.severe(e.message ?? e.toString(), error, trace); } return handleAngelHttpException( e, trace, req, res, request, response); }).catchError((e, StackTrace st) { - var trace = Trace.from(st ?? StackTrace.current).terse; + var trace = Trace.from(st).terse; closeResponse(response); // Ideally, we won't be in a position where an absolutely fatal error occurs, // but if so, we'll need to log it. - if (app.logger != null) { - app.logger.severe( + if (app!.logger != null) { + app!.logger!.severe( 'Fatal error occurred when processing $uri.', e, trace); } else { stderr @@ -218,12 +220,13 @@ abstract class Driver< ..writeln(trace); } }); + return; }, ); var zone = Zone.current.fork(specification: zoneSpec); - req.container.registerSingleton(zone); - req.container.registerSingleton(zoneSpec); + req.container!.registerSingleton(zone); + req.container!.registerSingleton(zoneSpec); // If a synchronous error is thrown, it's not caught by `zone.run`, // so use a try/catch, and recover when need be. @@ -240,17 +243,17 @@ abstract class Driver< } /// Handles an [AngelHttpException]. - Future handleAngelHttpException( + Future? handleAngelHttpException( AngelHttpException e, StackTrace st, - RequestContext req, - ResponseContext res, + RequestContext? req, + ResponseContext? res, Request request, Response response, {bool ignoreFinalizers = false}) { if (req == null || res == null) { try { - app.logger?.severe(null, e, st); + app!.logger?.severe(null, e, st); setStatusCode(response, 500); writeStringToResponse(response, '500 Internal Server Error'); closeResponse(response); @@ -266,8 +269,8 @@ abstract class Driver< } else { res.statusCode = e.statusCode; handleError = - Future.sync(() => app.errorHandler(e, req, res)).then((result) { - return app.executeHandler(result, req, res).then((_) => res.close()); + Future.sync(() => app!.errorHandler(e, req, res)).then((result) { + return app!.executeHandler(result, req, res).then((_) => res.close()); }); } @@ -280,11 +283,11 @@ abstract class Driver< ResponseContext res, {bool ignoreFinalizers = false}) { Future _cleanup(_) { - if (!app.environment.isProduction && - app.logger != null && - req.container.has()) { - var sw = req.container.make(); - app.logger.info( + if (!app!.environment.isProduction && + app!.logger != null && + req.container!.has()) { + var sw = req.container!.make(); + app!.logger!.info( "${res.statusCode} ${req.method} ${req.uri} (${sw?.elapsedMilliseconds ?? 'unknown'} ms)"); } return req.close(); @@ -294,7 +297,7 @@ abstract class Driver< Future finalizers = ignoreFinalizers == true ? Future.value() - : Future.forEach(app.responseFinalizers, (f) => f(req, res)); + : Future.forEach(app!.responseFinalizers, (dynamic f) => f(req, res)); return finalizers.then((_) { //if (res.isOpen) res.close(); @@ -303,18 +306,18 @@ abstract class Driver< setHeader(response, key, res.headers[key]); } - setContentLength(response, res.buffer.length); + setContentLength(response, res.buffer!.length); setChunkedEncoding(response, res.chunked ?? true); - List outputBuffer = res.buffer.toBytes(); + List outputBuffer = res.buffer!.toBytes(); if (res.encoders.isNotEmpty) { - var allowedEncodings = req.headers + var allowedEncodings = req.headers! .value('accept-encoding') ?.split(',') - ?.map((s) => s.trim()) - ?.where((s) => s.isNotEmpty) - ?.map((str) { + .map((s) => s.trim()) + .where((s) => s.isNotEmpty) + .map((str) { // Ignore quality specifications in accept-encoding // ex. gzip;q=0.8 if (!str.contains(';')) return str; @@ -323,7 +326,7 @@ abstract class Driver< if (allowedEncodings != null) { for (var encodingName in allowedEncodings) { - Converter, List> encoder; + Converter, List>? encoder; String key = encodingName; if (res.encoders.containsKey(encodingName)) { @@ -334,7 +337,7 @@ abstract class Driver< if (encoder != null) { setHeader(response, 'content-encoding', key); - outputBuffer = res.encoders[key].convert(outputBuffer); + outputBuffer = res.encoders[key]!.convert(outputBuffer); setContentLength(response, outputBuffer.length); break; } @@ -352,16 +355,16 @@ abstract class Driver< /// Runs a [MiddlewarePipeline]. static Future runPipeline( - MiddlewarePipelineIterator it, + MiddlewarePipelineIterator it, RequestContextType req, ResponseContextType res, - Angel app) async { + Angel? app) async { var broken = false; while (it.moveNext()) { var current = it.current.handlers.iterator; while (!broken && current.moveNext()) { - var result = await app.executeHandler(current.current, req, res); + var result = await app!.executeHandler(current.current, req, res); if (result != true) { broken = true; break; diff --git a/packages/framework/lib/src/core/env.dart b/packages/framework/lib/src/core/env.dart index a513c93a..39307b34 100644 --- a/packages/framework/lib/src/core/env.dart +++ b/packages/framework/lib/src/core/env.dart @@ -5,7 +5,7 @@ const AngelEnvironment angelEnv = AngelEnvironment(); /// Queries the environment's `ANGEL_ENV` value. class AngelEnvironment { - final String _customValue; + final String? _customValue; /// You can optionally provide a custom value, in order to override the system's /// value. diff --git a/packages/framework/lib/src/core/hooked_service.dart b/packages/framework/lib/src/core/hooked_service.dart index 91e6b99f..adb63df0 100644 --- a/packages/framework/lib/src/core/hooked_service.dart +++ b/packages/framework/lib/src/core/hooked_service.dart @@ -49,20 +49,20 @@ class HookedService> } @override - FutureOr Function(RequestContext, ResponseContext) get readData => + FutureOr? Function(RequestContext, ResponseContext?)? get readData => inner.readData; - RequestContext _getRequest(Map params) { + RequestContext? _getRequest(Map? params) { if (params == null) return null; - return params['__requestctx'] as RequestContext; + return params['__requestctx'] as RequestContext?; } - ResponseContext _getResponse(Map params) { + ResponseContext? _getResponse(Map? params) { if (params == null) return null; - return params['__responsectx'] as ResponseContext; + return params['__responsectx'] as ResponseContext?; } - Map _stripReq(Map params) { + Map? _stripReq(Map? params) { if (params == null) { return params; } else { @@ -95,7 +95,7 @@ class HookedService> /// Adds hooks to this instance. void addHooks(Angel app) { - var hooks = getAnnotation(inner, app.container.reflector); + var hooks = getAnnotation(inner, app.container!.reflector); List> before = [], after = []; if (hooks != null) { @@ -105,8 +105,8 @@ class HookedService> void applyListeners( Function fn, HookedServiceEventDispatcher dispatcher, - [bool isAfter]) { - Hooks hooks = getAnnotation(fn, app.container.reflector); + [bool? isAfter]) { + Hooks? hooks = getAnnotation(fn, app.container!.reflector); final listeners = >[] ..addAll(isAfter == true ? after : before); @@ -140,7 +140,7 @@ class HookedService> return true; }); - void addRoutes([Service s]) { + void addRoutes([Service? s]) { super.addRoutes(s ?? inner); } @@ -270,7 +270,7 @@ class HookedService> } @override - Future> index([Map _params]) { + Future> index([Map? _params]) { var params = _stripReq(_params); return beforeIndexed ._emit(HookedServiceEvent(false, _getRequest(_params), @@ -296,7 +296,7 @@ class HookedService> } @override - Future read(Id id, [Map _params]) { + Future read(Id? id, [Map? _params]) { var params = _stripReq(_params); return beforeRead ._emit(HookedServiceEvent(false, _getRequest(_params), @@ -322,7 +322,7 @@ class HookedService> } @override - Future create(Data data, [Map _params]) { + Future create(Data? data, [Map? _params]) { var params = _stripReq(_params); return beforeCreated ._emit(HookedServiceEvent(false, _getRequest(_params), @@ -337,7 +337,7 @@ class HookedService> .then((after) => after.result as Data); } - return inner.create(before.data, params).then((result) { + return inner.create(before.data!, params).then((result) { return afterCreated ._emit(HookedServiceEvent(true, _getRequest(_params), _getResponse(_params), inner, HookedServiceEvent.created, @@ -348,7 +348,7 @@ class HookedService> } @override - Future modify(Id id, Data data, [Map _params]) { + Future modify(Id? id, Data? data, [Map? _params]) { var params = _stripReq(_params); return beforeModified ._emit(HookedServiceEvent(false, _getRequest(_params), @@ -366,7 +366,7 @@ class HookedService> .then((after) => after.result as Data); } - return inner.modify(id, before.data, params).then((result) { + return inner.modify(id, before.data!, params).then((result) { return afterModified ._emit(HookedServiceEvent(true, _getRequest(_params), _getResponse(_params), inner, HookedServiceEvent.created, @@ -377,7 +377,7 @@ class HookedService> } @override - Future update(Id id, Data data, [Map _params]) { + Future update(Id? id, Data? data, [Map? _params]) { var params = _stripReq(_params); return beforeUpdated ._emit(HookedServiceEvent(false, _getRequest(_params), @@ -395,7 +395,7 @@ class HookedService> .then((after) => after.result as Data); } - return inner.update(id, before.data, params).then((result) { + return inner.update(id, before.data!, params).then((result) { return afterUpdated ._emit(HookedServiceEvent(true, _getRequest(_params), _getResponse(_params), inner, HookedServiceEvent.updated, @@ -406,7 +406,7 @@ class HookedService> } @override - Future remove(Id id, [Map _params]) { + Future remove(Id? id, [Map? _params]) { var params = _stripReq(_params); return beforeRemoved ._emit(HookedServiceEvent(false, _getRequest(_params), @@ -434,7 +434,7 @@ class HookedService> /// Fires an `after` event. This will not be propagated to clients, /// but will be broadcasted to WebSockets, etc. Future> fire(String eventName, result, - [HookedServiceEventListener callback]) { + [HookedServiceEventListener? callback]) { HookedServiceEventDispatcher dispatcher; switch (eventName) { @@ -469,8 +469,8 @@ class HookedService> Future> fireEvent( HookedServiceEventDispatcher dispatcher, HookedServiceEvent event, - [HookedServiceEventListener callback]) { - Future f; + [HookedServiceEventListener? callback]) { + Future? f; if (callback != null && event?._canceled != true) { f = Future.sync(() => callback(event)); } @@ -480,7 +480,7 @@ class HookedService> } /// Fired when a hooked service is invoked. -class HookedServiceEvent> { +class HookedServiceEvent> { static const String indexed = 'indexed'; static const String read = 'read'; static const String created = 'created'; @@ -506,38 +506,38 @@ class HookedServiceEvent> { /// Resolves a service from the application. /// /// Shorthand for `e.service.app.service(...)`. - Service getService(Pattern path) => service.app.findService(path); + Service? getService(Pattern path) => service.app!.findService(path); bool _canceled = false; String _eventName; - Id _id; + Id? _id; bool _isAfter; - Data data; - Map _params; - RequestContext _request; - ResponseContext _response; + Data? data; + Map? _params; + RequestContext? _request; + ResponseContext? _response; var result; String get eventName => _eventName; - Id get id => _id; + Id? get id => _id; bool get isAfter => _isAfter == true; bool get isBefore => !isAfter; - Map get params => _params; + Map? get params => _params; - RequestContext get request => _request; + RequestContext? get request => _request; - ResponseContext get response => _response; + ResponseContext? get response => _response; /// The inner service whose method was hooked. T service; HookedServiceEvent(this._isAfter, this._request, this._response, this.service, this._eventName, - {Id id, this.data, Map params, this.result}) { + {Id? id, this.data, Map? params, this.result}) { _id = id; _params = params ?? {}; } diff --git a/packages/framework/lib/src/core/hostname_parser.dart b/packages/framework/lib/src/core/hostname_parser.dart index 5b3540fb..86525757 100644 --- a/packages/framework/lib/src/core/hostname_parser.dart +++ b/packages/framework/lib/src/core/hostname_parser.dart @@ -17,7 +17,7 @@ class HostnameSyntaxParser { RegExp parse() { var b = StringBuffer(); - var parts = Queue(); + var parts = Queue(); while (!_scanner.isDone) { if (_scanner.scan('|')) { @@ -25,7 +25,7 @@ class HostnameSyntaxParser { throw _formatExc('No hostname parts found before "|".'); } else { var next = _parseHostnamePart(); - if (next == null) { + if (next.isEmpty) { throw _formatExc('No hostname parts found after "|".'); } else { var prev = parts.removeLast(); @@ -33,11 +33,11 @@ class HostnameSyntaxParser { } } } else { - var part = _parseHostnamePart(); - if (part != null) { + String part = _parseHostnamePart(); + if (part.isNotEmpty) { if (_scanner.scan('.')) { var subPart = _parseHostnamePart(shouldThrow: false); - while (subPart != null) { + while (subPart.isNotEmpty) { part += '\\.' + subPart; if (_scanner.scan('.')) { subPart = _parseHostnamePart(shouldThrow: false); @@ -46,7 +46,6 @@ class HostnameSyntaxParser { } } } - parts.add(part); } } @@ -71,12 +70,12 @@ class HostnameSyntaxParser { } else if (_scanner.scan('+')) { return r'[^$]+'; } else if (_scanner.scan(_safe)) { - return _scanner.lastMatch[0]; + return _scanner.lastMatch?[0] ?? ""; } else if (!_scanner.isDone && shouldThrow) { - var s = String.fromCharCode(_scanner.peekChar()); + var s = String.fromCharCode(_scanner.peekChar()!); throw _formatExc('Unexpected character "$s".'); } else { - return null; + return ""; } } } diff --git a/packages/framework/lib/src/core/hostname_router.dart b/packages/framework/lib/src/core/hostname_router.dart index b896a895..00f8900a 100644 --- a/packages/framework/lib/src/core/hostname_router.dart +++ b/packages/framework/lib/src/core/hostname_router.dart @@ -60,10 +60,10 @@ class HostnameRouter { Map Function(Angel)> configurers, {Reflector reflector = const EmptyReflector(), AngelEnvironment environment = angelEnv, - Logger logger, + Logger? logger, bool allowMethodOverrides = true, - FutureOr Function(dynamic) serializer, - ViewGenerator viewGenerator}) { + FutureOr Function(dynamic)? serializer, + ViewGenerator? viewGenerator}) { var creators = configurers.map((p, c) { return MapEntry(p, () async { var app = Angel( @@ -89,17 +89,17 @@ class HostnameRouter { if (req.hostname != null) { for (var pattern in _patterns) { // print('${req.hostname} vs $_creators'); - if (pattern.allMatches(req.hostname).isNotEmpty) { + if (pattern.allMatches(req.hostname!).isNotEmpty) { // Resolve the entire pipeline within the context of the selected app. - var app = _apps[pattern] ??= (await _creators[pattern]()); + var app = _apps[pattern] ??= (await _creators[pattern]!()); // print('App for ${req.hostname} = $app from $pattern'); // app.dumpTree(); var r = app.optimizedRouter; - var resolved = r.resolveAbsolute(req.path, method: req.method); - var pipeline = MiddlewarePipeline(resolved); + var resolved = r.resolveAbsolute(req.path, method: req.method!); + var pipeline = MiddlewarePipeline(resolved); // print('Pipeline: $pipeline'); - for (var handler in pipeline.handlers) { + for (var handler in pipeline.handlers!) { // print(handler); // Avoid stack overflow. if (handler == handleRequest) { diff --git a/packages/framework/lib/src/core/injection.dart b/packages/framework/lib/src/core/injection.dart index 1f08de21..28189c2b 100644 --- a/packages/framework/lib/src/core/injection.dart +++ b/packages/framework/lib/src/core/injection.dart @@ -8,13 +8,13 @@ const List _primitiveTypes = [String, int, num, double, Null]; /// /// Calling [ioc] also auto-serializes the result of a [handler]. RequestHandler ioc(Function handler, {Iterable optional = const []}) { - InjectionRequest injection; - RequestHandler contained; + InjectionRequest? injection; + RequestHandler? contained; return (req, res) { if (injection == null) { - injection = preInject(handler, req.app.container.reflector); - injection.optional.addAll(optional ?? []); + injection = preInject(handler, req.app.container!.reflector); + injection!.optional.addAll(optional); contained = handleContained(handler, injection); } @@ -22,9 +22,9 @@ RequestHandler ioc(Function handler, {Iterable optional = const []}) { }; } -resolveInjection(requirement, InjectionRequest injection, RequestContext req, +resolveInjection(requirement, InjectionRequest? injection, RequestContext req, ResponseContext res, bool throwOnUnresolved, - [Container container]) async { + [Container? container]) async { var propFromApp; container ??= req?.container ?? res?.app?.container; @@ -33,20 +33,20 @@ resolveInjection(requirement, InjectionRequest injection, RequestContext req, } else if (requirement == ResponseContext) { return res; } else if (requirement is String && - injection.parameters.containsKey(requirement)) { - var param = injection.parameters[requirement]; + injection!.parameters.containsKey(requirement)) { + var param = injection.parameters[requirement]!; var value = param.getValue(req); - if (value == null && param.required != false) throw param.error; + if (value == null && param.required != false) throw param.error as Object; return value; } else if (requirement is String) { - if (req.container.hasNamed(requirement)) { - return req.container.findByName(requirement); + if (req.container!.hasNamed(requirement)) { + return req.container!.findByName(requirement); } if (req.params.containsKey(requirement)) { return req.params[requirement]; } else if ((propFromApp = req.app.findProperty(requirement)) != null) { return propFromApp; - } else if (injection.optional.contains(requirement)) { + } else if (injection!.optional.contains(requirement)) { return null; } else if (throwOnUnresolved) { throw ArgumentError( @@ -69,7 +69,7 @@ resolveInjection(requirement, InjectionRequest injection, RequestContext req, } } else if (requirement is Type && requirement != dynamic) { try { - var futureType = container.reflector.reflectFutureOf(requirement); + var futureType = container!.reflector.reflectFutureOf(requirement); if (container.has(futureType.reflectedType)) { return await container.make(futureType.reflectedType); } @@ -77,7 +77,7 @@ resolveInjection(requirement, InjectionRequest injection, RequestContext req, // Ignore. } - return await container.make(requirement); + return await container!.make(requirement); } else if (throwOnUnresolved) { throw ArgumentError( '$requirement cannot be injected into a request handler.'); @@ -95,10 +95,10 @@ bool suitableForInjection( } /// Handles a request with a DI-enabled handler. -RequestHandler handleContained(Function handler, InjectionRequest injection, - [Container container]) { +RequestHandler handleContained(Function? handler, InjectionRequest? injection, + [Container? container]) { return (RequestContext req, ResponseContext res) async { - if (injection.parameters.isNotEmpty && + if (injection!.parameters.isNotEmpty && injection.parameters.values.any((p) => p.match != null) && !suitableForInjection(req, res, injection)) return Future.value(true); @@ -116,7 +116,7 @@ RequestHandler handleContained(Function handler, InjectionRequest injection, [entry.key, entry.value], injection, req, res, false, container); } - return Function.apply(handler, args, named); + return Function.apply(handler!, args, named); }; } @@ -157,7 +157,7 @@ class InjectionRequest { InjectionRequest preInject(Function handler, Reflector reflector) { var injection = InjectionRequest(); - var closureMirror = reflector.reflectFunction(handler); + var closureMirror = reflector.reflectFunction(handler)!; if (closureMirror.parameters.isEmpty) return injection; @@ -169,9 +169,8 @@ InjectionRequest preInject(Function handler, Reflector reflector) { var _Parameter = reflector.reflectType(Parameter); var p = parameter.annotations - .firstWhere((m) => m.type.isAssignableTo(_Parameter), - orElse: () => null) - ?.reflectee as Parameter; + .firstWhereOrNull((m) => m.type.isAssignableTo(_Parameter)) + ?.reflectee as Parameter?; //print(p); if (p != null) { injection.parameters[name] = Parameter( diff --git a/packages/framework/lib/src/core/map_service.dart b/packages/framework/lib/src/core/map_service.dart index a7b50915..d3a02aa8 100644 --- a/packages/framework/lib/src/core/map_service.dart +++ b/packages/framework/lib/src/core/map_service.dart @@ -5,7 +5,7 @@ import 'package:angel_http_exception/angel_http_exception.dart'; import 'service.dart'; /// A basic service that manages an in-memory list of maps. -class MapService extends Service> { +class MapService extends Service> { /// If set to `true`, clients can remove all items by passing a `null` `id` to `remove`. /// /// `false` by default. @@ -48,14 +48,14 @@ class MapService extends Service> { } @override - Future>> index([Map params]) { + Future>> index([Map? params]) { if (allowQuery == false || params == null || params['query'] is! Map) { return Future.value(items); } else { - var query = params['query'] as Map; + var query = params['query'] as Map?; return Future.value(items.where((item) { - for (var key in query.keys) { + for (var key in query!.keys) { if (!item.containsKey(key)) { return false; } else if (item[key] != query[key]) return false; @@ -67,15 +67,15 @@ class MapService extends Service> { } @override - Future> read(String id, [Map params]) { + Future> read(String? id, [Map? params]) { return Future.value(items.firstWhere(_matchesId(id), - orElse: () => throw AngelHttpException.notFound( - message: 'No record found for ID $id'))); + orElse: (() => throw AngelHttpException.notFound( + message: 'No record found for ID $id')) as Map Function()?)); } @override Future> create(Map data, - [Map params]) { + [Map? params]) { if (data is! Map) { throw AngelHttpException.badRequest( message: @@ -95,8 +95,8 @@ class MapService extends Service> { } @override - Future> modify(String id, Map data, - [Map params]) { + Future> modify(String? id, Map data, + [Map? params]) { if (data is! Map) { throw AngelHttpException.badRequest( message: @@ -119,8 +119,8 @@ class MapService extends Service> { } @override - Future> update(String id, Map data, - [Map params]) { + Future> update(String? id, Map data, + [Map? params]) { if (data is! Map) { throw AngelHttpException.badRequest( message: @@ -149,8 +149,8 @@ class MapService extends Service> { } @override - Future> remove(String id, - [Map params]) { + Future> remove(String? id, + [Map? params]) { if (id == null || id == 'null') { // Remove everything... if (!(allowRemoveAll == true || diff --git a/packages/framework/lib/src/core/metadata.dart b/packages/framework/lib/src/core/metadata.dart index 0670f414..56bb9fee 100644 --- a/packages/framework/lib/src/core/metadata.dart +++ b/packages/framework/lib/src/core/metadata.dart @@ -45,9 +45,9 @@ const NoExpose noExpose = NoExpose(); /// ``` class Expose { final String method; - final String path; - final Iterable middleware; - final String as; + final String? path; + final Iterable? middleware; + final String? as; final List allowNull; static const Expose get = Expose(null, method: 'GET'), @@ -71,16 +71,16 @@ class Expose { /// Used to apply special dependency injections or functionality to a function parameter. class Parameter { /// Inject the value of a request cookie. - final String cookie; + final String? cookie; /// Inject the value of a request header. - final String header; + final String? header; /// Inject the value of a key from the session. - final String session; + final String? session; /// Inject the value of a key from the query. - final String query; + final String? query; /// Only execute the handler if the value of this parameter matches the given value. final match; @@ -89,7 +89,7 @@ class Parameter { final defaultValue; /// If `true` (default), then an error will be thrown if this parameter is not present. - final bool required; + final bool? required; const Parameter( {this.cookie, @@ -122,17 +122,17 @@ class Parameter { /// Obtains a value for this parameter from a [RequestContext]. getValue(RequestContext req) { if (cookie?.isNotEmpty == true) { - return req.cookies.firstWhere((c) => c.name == cookie)?.value ?? + return req.cookies!.firstWhere((c) => c.name == cookie)?.value ?? defaultValue; } if (header?.isNotEmpty == true) { - return req.headers.value(header) ?? defaultValue; + return req.headers!.value(header!) ?? defaultValue; } if (session?.isNotEmpty == true) { - return req.session[session] ?? defaultValue; + return req.session![session] ?? defaultValue; } if (query?.isNotEmpty == true) { - return req.uri.queryParameters[query] ?? defaultValue; + return req.uri!.queryParameters[query!] ?? defaultValue; } return defaultValue; } diff --git a/packages/framework/lib/src/core/request_context.dart b/packages/framework/lib/src/core/request_context.dart index 0d9c072b..6fb2e974 100644 --- a/packages/framework/lib/src/core/request_context.dart +++ b/packages/framework/lib/src/core/request_context.dart @@ -17,6 +17,7 @@ import 'package:http_server/http_server.dart'; import 'package:meta/meta.dart'; import 'package:mime/mime.dart'; import 'package:path/path.dart' as p; +import 'package:collection/collection.dart'; import 'metadata.dart'; import 'response_context.dart'; @@ -31,13 +32,13 @@ abstract class RequestContext { /// when a [RequestContext] is done being processed. final List Function()> shutdownHooks = []; - String _acceptHeaderCache, _extensionCache; - bool _acceptsAllCache, _hasParsedBody = false, _closed = false; - Map _bodyFields, _queryParameters; - List _bodyList; - Object _bodyObject; - List _uploadedFiles; - MediaType _contentType; + String? _acceptHeaderCache, _extensionCache; + bool? _acceptsAllCache, _hasParsedBody = false, _closed = false; + Map? _bodyFields, _queryParameters; + List? _bodyList; + Object? _bodyObject; + List? _uploadedFiles; + MediaType? _contentType; /// The underlying [RawRequest] provided by the driver. RawRequest get rawRequest; @@ -46,22 +47,22 @@ abstract class RequestContext { final Map serviceParams = {}; /// The [Angel] instance that is responding to this request. - Angel app; + late Angel app; /// Any cookies sent with this request. - List get cookies; + List? get cookies; /// All HTTP headers sent with this request. - HttpHeaders get headers; + HttpHeaders? get headers; /// The requested hostname. - String get hostname; + String? get hostname; /// The IoC container that can be used to provide functionality to produce /// objects of a given type. /// /// This is a *child* of the container found in `app`. - Container get container; + Container? get container; /// The user's IP. String get ip => remoteAddress.address; @@ -69,24 +70,24 @@ abstract class RequestContext { /// This request's HTTP method. /// /// This may have been processed by an override. See [originalMethod] to get the real method. - String get method; + String? get method; /// The original HTTP verb sent to the server. - String get originalMethod; + String? get originalMethod; /// The content type of an incoming request. - MediaType get contentType => - _contentType ??= MediaType.parse(headers.contentType.toString()); + MediaType? get contentType => + _contentType ??= MediaType.parse(headers!.contentType.toString()); /// The URL parameters extracted from the request URI. - Map params = {}; + Map params = {}; /// The requested path. - String get path; + String? get path; /// Is this an **XMLHttpRequest**? bool get isXhr { - return headers.value("X-Requested-With")?.trim()?.toLowerCase() == + return headers!.value("X-Requested-With")?.trim()?.toLowerCase() == 'xmlhttprequest'; } @@ -94,22 +95,22 @@ abstract class RequestContext { InternetAddress get remoteAddress; /// The user's HTTP session. - HttpSession get session; + HttpSession? get session; /// The [Uri] instance representing the path this request is responding to. - Uri get uri; + Uri? get uri; /// The [Stream] of incoming binary data sent from the client. - Stream> get body; + Stream>? get body; /// Returns `true` if [parseBody] has been called so far. - bool get hasParsedBody => _hasParsedBody; + bool? get hasParsedBody => _hasParsedBody; /// Returns a *mutable* [Map] of the fields parsed from the request [body]. /// /// Note that [parseBody] must be called first. - Map get bodyAsMap { - if (!hasParsedBody) { + Map? get bodyAsMap { + if (!hasParsedBody!) { throw StateError('The request body has not been parsed yet.'); } else if (_bodyFields == null) { throw StateError('The request body, $_bodyObject, is not a Map.'); @@ -121,13 +122,13 @@ abstract class RequestContext { /// This setter allows you to explicitly set the request body **exactly once**. /// /// Use this if the format of the body is not natively parsed by Angel. - set bodyAsMap(Map value) => bodyAsObject = value; + set bodyAsMap(Map? value) => bodyAsObject = value; /// Returns a *mutable* [List] parsed from the request [body]. /// /// Note that [parseBody] must be called first. - List get bodyAsList { - if (!hasParsedBody) { + List? get bodyAsList { + if (!hasParsedBody!) { throw StateError('The request body has not been parsed yet.'); } else if (_bodyList == null) { throw StateError('The request body, $_bodyObject, is not a List.'); @@ -139,13 +140,13 @@ abstract class RequestContext { /// This setter allows you to explicitly set the request body **exactly once**. /// /// Use this if the format of the body is not natively parsed by Angel. - set bodyAsList(List value) => bodyAsObject = value; + set bodyAsList(List? value) => bodyAsObject = value; /// Returns the parsed request body, whatever it may be (typically a [Map] or [List]). /// /// Note that [parseBody] must be called first. - Object get bodyAsObject { - if (!hasParsedBody) { + Object? get bodyAsObject { + if (!hasParsedBody!) { throw StateError('The request body has not been parsed yet.'); } @@ -161,7 +162,7 @@ abstract class RequestContext { 'The request body has already been parsed/set, and cannot be overwritten.'); } else { if (value is List) _bodyList = value; - if (value is Map) _bodyFields = value; + if (value is Map) _bodyFields = value; _bodyObject = value; _hasParsedBody = true; } @@ -170,8 +171,8 @@ abstract class RequestContext { /// Returns a *mutable* map of the files parsed from the request [body]. /// /// Note that [parseBody] must be called first. - List get uploadedFiles { - if (!hasParsedBody) { + List? get uploadedFiles { + if (!hasParsedBody!) { throw StateError('The request body has not been parsed yet.'); } @@ -179,13 +180,13 @@ abstract class RequestContext { } /// Returns a *mutable* map of the fields contained in the query. - Map get queryParameters => - _queryParameters ??= Map.from(uri.queryParameters); + Map get queryParameters => + _queryParameters ??= Map.from(uri!.queryParameters); /// Returns the file extension of the requested path, if any. /// /// Includes the leading `.`, if there is one. - String get extension => _extensionCache ??= p.extension(uri.path); + String get extension => _extensionCache ??= p.extension(uri!.path); /// Returns `true` if the client's `Accept` header indicates that the given [contentType] is considered a valid response. /// @@ -207,14 +208,14 @@ abstract class RequestContext { 'RequestContext.accepts expects the `contentType` parameter to NOT be null.'); } - _acceptHeaderCache ??= headers.value('accept'); + _acceptHeaderCache ??= headers!.value('accept'); if (_acceptHeaderCache == null) { return true; - } else if (strict != true && _acceptHeaderCache.contains('*/*')) { + } else if (strict != true && _acceptHeaderCache!.contains('*/*')) { return true; } else { - return _acceptHeaderCache.contains(contentTypeString); + return _acceptHeaderCache!.contains(contentTypeString); } } @@ -222,14 +223,14 @@ abstract class RequestContext { bool get acceptsAll => _acceptsAllCache ??= accepts('*/*'); /// Shorthand for deserializing [bodyAsMap], using some transformer function [f]. - Future deserializeBody(FutureOr Function(Map) f, + Future deserializeBody(FutureOr Function(Map?) f, {Encoding encoding = utf8}) async { await parseBody(encoding: encoding); return await f(bodyAsMap); } /// Shorthand for decoding [bodyAsMap], using some [codec]. - Future decodeBody(Codec codec, {Encoding encoding = utf8}) => + Future decodeBody(Codec codec, {Encoding encoding = utf8}) => deserializeBody(codec.decode, encoding: encoding); /// Manually parses the request body, if it has not already been parsed. @@ -238,47 +239,48 @@ abstract class RequestContext { throw FormatException('Missing "content-type" header.'); } - if (!_hasParsedBody) { + if (!_hasParsedBody!) { _hasParsedBody = true; - if (contentType.type == 'application' && contentType.subtype == 'json') { + if (contentType!.type == 'application' && + contentType!.subtype == 'json') { _uploadedFiles = []; - var parsed = _bodyObject = - await encoding.decoder.bind(body).join().then(json.decode); + var parsed = (_bodyObject = + await encoding.decoder.bind(body!).join().then(json.decode))!; if (parsed is Map) { - _bodyFields = Map.from(parsed); + _bodyFields = Map.from(parsed); } else if (parsed is List) { _bodyList = parsed; } - } else if (contentType.type == 'application' && - contentType.subtype == 'x-www-form-urlencoded') { + } else if (contentType!.type == 'application' && + contentType!.subtype == 'x-www-form-urlencoded') { _uploadedFiles = []; var parsed = await encoding.decoder - .bind(body) + .bind(body!) .join() .then((s) => Uri.splitQueryString(s, encoding: encoding)); - _bodyFields = Map.from(parsed); - } else if (contentType.type == 'multipart' && - contentType.subtype == 'form-data' && - contentType.parameters.containsKey('boundary')) { - var boundary = contentType.parameters['boundary']; + _bodyFields = Map.from(parsed); + } else if (contentType!.type == 'multipart' && + contentType!.subtype == 'form-data' && + contentType!.parameters.containsKey('boundary')) { + var boundary = contentType!.parameters['boundary']!; var transformer = MimeMultipartTransformer(boundary); - var parts = transformer.bind(body).map((part) => + var parts = transformer.bind(body!).map((part) => HttpMultipartFormData.parse(part, defaultEncoding: encoding)); _bodyFields = {}; _uploadedFiles = []; await for (var part in parts) { if (part.isBinary) { - _uploadedFiles.add(UploadedFile(part)); + _uploadedFiles!.add(UploadedFile(part)); } else if (part.isText && part.contentDisposition.parameters.containsKey('name')) { // If there is no name, then don't parse it. var key = part.contentDisposition.parameters['name']; var value = await part.join(); - _bodyFields[key] = value; + _bodyFields![key] = value; } } } else { @@ -291,13 +293,13 @@ abstract class RequestContext { /// Disposes of all resources. @mustCallSuper Future close() async { - if (!_closed) { + if (!_closed!) { _closed = true; _acceptsAllCache = null; _acceptHeaderCache = null; serviceParams.clear(); params.clear(); - await Future.forEach(shutdownHooks, (hook) => hook()); + await Future.forEach(shutdownHooks, (dynamic hook) => hook()); } } } @@ -307,7 +309,7 @@ class UploadedFile { /// The underlying `form-data` item. final HttpMultipartFormData formData; - MediaType _contentType; + MediaType? _contentType; UploadedFile(this.formData); @@ -316,22 +318,22 @@ class UploadedFile { /// The filename associated with the data on the user's system. /// Returns [:null:] if not present. - String get filename => formData.contentDisposition.parameters['filename']; + String? get filename => formData.contentDisposition.parameters['filename']; /// The name of the field associated with this data. /// Returns [:null:] if not present. - String get name => formData.contentDisposition.parameters['name']; + String? get name => formData.contentDisposition.parameters['name']; /// The parsed [:Content-Type:] header of the [:HttpMultipartFormData:]. /// Returns [:null:] if not present. - MediaType get contentType => _contentType ??= (formData.contentType == null + MediaType? get contentType => _contentType ??= (formData.contentType == null ? null : MediaType.parse(formData.contentType.toString())); /// The parsed [:Content-Transfer-Encoding:] header of the /// [:HttpMultipartFormData:]. This field is used to determine how to decode /// the data. Returns [:null:] if not present. - HeaderValue get contentTransferEncoding => formData.contentTransferEncoding; + HeaderValue? get contentTransferEncoding => formData.contentTransferEncoding; /// Reads the contents of the file into a single linear buffer. /// diff --git a/packages/framework/lib/src/core/response_context.dart b/packages/framework/lib/src/core/response_context.dart index 2110dc0e..bd635e82 100644 --- a/packages/framework/lib/src/core/response_context.dart +++ b/packages/framework/lib/src/core/response_context.dart @@ -26,14 +26,14 @@ abstract class ResponseContext 'server': 'angel', }); - Completer _done; + Completer? _done; int _statusCode = 200; /// The [Angel] instance that is sending a response. - Angel app; + Angel? app; /// Is `Transfer-Encoding` chunked? - bool chunked; + bool? chunked; /// Any and all cookies to be sent to the user. final List cookies = []; @@ -49,7 +49,7 @@ abstract class ResponseContext final Map renderParams = {}; /// Points to the [RequestContext] corresponding to this response. - RequestContext get correspondingRequest; + RequestContext? get correspondingRequest; @override Future get done => (_done ?? Completer()).future; @@ -71,12 +71,12 @@ abstract class ResponseContext /// ```dart /// app.injectSerializer(JSON.encode); /// ``` - FutureOr Function(dynamic) serializer = c.json.encode; + FutureOr Function(dynamic)? serializer = c.json.encode; /// This response's status code. int get statusCode => _statusCode; - set statusCode(int value) { + set statusCode(int? value) { if (!isOpen) { throw closed(); } else { @@ -94,7 +94,7 @@ abstract class ResponseContext bool get isBuffered; /// A set of UTF-8 encoded bytes that will be written to the response. - BytesBuilder get buffer; + BytesBuilder? get buffer; /// The underlying [RawResponse] under this instance. RawResponse get rawResponse; @@ -108,14 +108,14 @@ abstract class ResponseContext /// Gets or sets the content length to send back to a client. /// /// Returns `null` if the header is invalidly formatted. - int get contentLength { - return int.tryParse(headers['content-length']); + int? get contentLength { + return int.tryParse(headers['content-length']!); } /// Gets or sets the content length to send back to a client. /// /// If [value] is `null`, then the header will be removed. - set contentLength(int value) { + set contentLength(int? value) { if (value == null) { headers.remove('content-length'); } else { @@ -126,7 +126,7 @@ abstract class ResponseContext /// Gets or sets the content type to send back to a client. MediaType get contentType { try { - return MediaType.parse(headers['content-type']); + return MediaType.parse(headers['content-type']!); } catch (_) { return MediaType('text', 'plain'); } @@ -140,18 +140,18 @@ abstract class ResponseContext static StateError closed() => StateError('Cannot modify a closed response.'); /// Sends a download as a response. - Future download(File file, {String filename}) async { + Future download(File file, {String? filename}) async { if (!isOpen) throw closed(); headers["Content-Disposition"] = 'attachment; filename="${filename ?? file.path}"'; - contentType = MediaType.parse(lookupMimeType(file.path)); + contentType = MediaType.parse(lookupMimeType(file.path)!); headers['content-length'] = file.lengthSync().toString(); if (!isBuffered) { await file.openRead().cast>().pipe(this); } else { - buffer.add(file.readAsBytesSync()); + buffer!.add(file.readAsBytesSync()); await close(); } } @@ -162,7 +162,7 @@ abstract class ResponseContext (buffer as LockableBytesBuilder).lock(); } - if (_done?.isCompleted == false) _done.complete(); + if (_done?.isCompleted == false) _done!.complete(); return Future.value(); } @@ -175,18 +175,18 @@ abstract class ResponseContext /// /// You can override the [contentType] sent; by default it is `application/javascript`. Future jsonp(value, - {String callbackName = "callback", MediaType contentType}) { + {String callbackName = "callback", MediaType? contentType}) { if (!isOpen) throw closed(); this.contentType = contentType ?? MediaType('application', 'javascript'); - write("$callbackName(${serializer(value)})"); + write("$callbackName(${serializer!(value)})"); return close(); } /// Renders a view to the response stream, and closes the response. - Future render(String view, [Map data]) { + Future render(String view, [Map? data]) { if (!isOpen) throw closed(); contentType = MediaType('text', 'html', {'charset': 'utf-8'}); - return Future.sync(() => app.viewGenerator( + return Future.sync(() => app!.viewGenerator!( view, Map.from(renderParams) ..addAll(data ?? {}))).then((content) { @@ -202,13 +202,13 @@ abstract class ResponseContext /// based on the provided params. /// /// See [Router]#navigate for more. :) - Future redirect(url, {bool absolute = true, int code = 302}) { + Future redirect(url, {bool absolute = true, int? code = 302}) { if (!isOpen) throw closed(); headers ..['content-type'] = 'text/html' ..['location'] = (url is String || url is Uri) ? url.toString() - : app.navigate(url as Iterable, absolute: absolute); + : app!.navigate(url as Iterable, absolute: absolute); statusCode = code ?? 302; write(''' @@ -231,9 +231,9 @@ abstract class ResponseContext } /// Redirects to the given named [Route]. - Future redirectTo(String name, [Map params, int code]) async { + Future redirectTo(String name, [Map? params, int? code]) async { if (!isOpen) throw closed(); - Route _findRoute(Router r) { + Route? _findRoute(Router r) { for (Route route in r.routes) { if (route is SymlinkRoute) { final m = _findRoute(route.router); @@ -245,11 +245,11 @@ abstract class ResponseContext return null; } - Route matched = _findRoute(app); + Route? matched = _findRoute(app!); if (matched != null) { await redirect( - matched.makeUri(params.keys.fold>({}, (out, k) { + matched.makeUri(params!.keys.fold>({}, (out, k) { return out..[k.toString()] = params[k]; })), code: code); @@ -260,7 +260,7 @@ abstract class ResponseContext } /// Redirects to the given [Controller] action. - Future redirectToAction(String action, [Map params, int code]) { + Future redirectToAction(String action, [Map? params, int? code]) { if (!isOpen) throw closed(); // UserController@show List split = action.split("@"); @@ -270,14 +270,14 @@ abstract class ResponseContext "Controller redirects must take the form of 'Controller@action'. You gave: $action"); } - Controller controller = - app.controllers[split[0].replaceAll(_straySlashes, '')]; + Controller? controller = + app!.controllers[split[0].replaceAll(_straySlashes, '')]; if (controller == null) { throw Exception("Could not find a controller named '${split[0]}'"); } - Route matched = controller.routeMappings[split[1]]; + Route? matched = controller.routeMappings[split[1]]; if (matched == null) { throw Exception( @@ -285,24 +285,26 @@ abstract class ResponseContext } final head = controller - .findExpose(app.container.reflector) + .findExpose(app!.container!.reflector)! .path .toString() .replaceAll(_straySlashes, ''); - final tail = matched - .makeUri(params.keys.fold>({}, (out, k) { - return out..[k.toString()] = params[k]; - })) - .replaceAll(_straySlashes, ''); - + String tail = ""; + if (params != null) { + tail = matched + .makeUri(params.keys.fold>({}, (out, k) { + return out..[k.toString()] = params[k]; + })) + .replaceAll(_straySlashes, ''); + } return redirect('$head/$tail'.replaceAll(_straySlashes, ''), code: code); } /// Serializes data to the response. - Future serialize(value, {MediaType contentType}) async { + Future serialize(value, {MediaType? contentType}) async { if (!isOpen) throw closed(); this.contentType = contentType ?? MediaType('application', 'json'); - var text = await serializer(value); + var text = await serializer!(value); if (text.isEmpty) return true; write(text); await close(); @@ -314,13 +316,13 @@ abstract class ResponseContext /// `HEAD` responses will not actually write data. Future streamFile(File file) async { if (!isOpen) throw closed(); - var mimeType = app.mimeTypeResolver.lookup(file.path); + var mimeType = app!.mimeTypeResolver.lookup(file.path); contentLength = await file.length(); contentType = mimeType == null ? MediaType('application', 'octet-stream') : MediaType.parse(mimeType); - if (correspondingRequest.method != 'HEAD') { + if (correspondingRequest!.method != 'HEAD') { return this .addStream(file.openRead().cast>()) .then((_) => this.close()); @@ -338,16 +340,16 @@ abstract class ResponseContext Future addStream(Stream> stream); @override - void addError(Object error, [StackTrace stackTrace]) { + void addError(Object error, [StackTrace? stackTrace]) { if (_done?.isCompleted == false) { - _done.completeError(error, stackTrace); + _done!.completeError(error, stackTrace); } else if (_done == null) { - Zone.current.handleUncaughtError(error, stackTrace); + Zone.current.handleUncaughtError(error, stackTrace!); } } /// Writes data to the response. - void write(value, {Encoding encoding}) { + void write(value, {Encoding? encoding}) { encoding ??= utf8; if (!isOpen && isBuffered) { @@ -355,7 +357,7 @@ abstract class ResponseContext } else if (!isBuffered) { add(encoding.encode(value.toString())); } else { - buffer.add(encoding.encode(value.toString())); + buffer!.add(encoding.encode(value.toString())); } } @@ -366,12 +368,12 @@ abstract class ResponseContext } else if (!isBuffered) { add([charCode]); } else { - buffer.addByte(charCode); + buffer!.addByte(charCode); } } @override - void writeln([Object obj = ""]) { + void writeln([Object? obj = ""]) { write(obj.toString()); write('\r\n'); } diff --git a/packages/framework/lib/src/core/routable.dart b/packages/framework/lib/src/core/routable.dart index a497c4d0..c01d4bee 100644 --- a/packages/framework/lib/src/core/routable.dart +++ b/packages/framework/lib/src/core/routable.dart @@ -21,7 +21,7 @@ typedef FutureOr RequestHandler(RequestContext req, ResponseContext res); /// return `true`. Works well with [Router].chain. RequestHandler chain(Iterable handlers) { return (req, res) { - Future Function() runPipeline; + Future Function()? runPipeline; for (var handler in handlers) { if (handler == null) break; @@ -42,19 +42,19 @@ RequestHandler chain(Iterable handlers) { } /// A routable server that can handle dynamic requests. -class Routable extends Router { +class Routable extends Router { final Map _services = {}; - final Map _serviceLookups = {}; + final Map _serviceLookups = {}; final Map configuration = {}; - final Container _container; + final Container? _container; - Routable([Reflector reflector]) + Routable([Reflector? reflector]) : _container = reflector == null ? null : Container(reflector), super(); /// A [Container] used to inject dependencies. - Container get container => _container; + Container? get container => _container; void close() { _services.clear(); @@ -73,34 +73,34 @@ class Routable extends Router { Stream get onService => _onService.stream; /// Retrieves the service assigned to the given path. - T findService(Pattern path) { + T? findService(Pattern path) { return _serviceLookups.putIfAbsent(path, () { return _services[path] ?? _services[path.toString().replaceAll(_straySlashes, '')]; - }) as T; + }) as T?; } /// Shorthand for finding a [Service] in a statically-typed manner. - Service findServiceOf(Pattern path) { + Service? findServiceOf(Pattern path) { return findService>(path); } /// Shorthand for finding a [HookedService] in a statically-typed manner. - HookedService findHookedService( + HookedService? findHookedService( Pattern path) { - return findService(path) as HookedService; + return findService(path) as HookedService?; } @override - Route addRoute( - String method, String path, RequestHandler handler, - {Iterable middleware}) { + Route addRoute( + String? method, String? path, RequestHandler? handler, + {Iterable? middleware}) { middleware ??= []; final handlers = []; // Merge @Middleware declaration, if any var reflector = _container?.reflector; if (reflector != null && reflector is! ThrowingReflector) { - Middleware middlewareDeclaration = + Middleware? middlewareDeclaration = getAnnotation(handler, _container?.reflector); if (middlewareDeclaration != null) { handlers.addAll(middlewareDeclaration.handlers); @@ -108,7 +108,7 @@ class Routable extends Router { } final handlerSequence = []; - handlerSequence.addAll(middleware ?? []); + handlerSequence.addAll(middleware as Iterable? Function(RequestContext, ResponseContext)>? ?? []); handlerSequence.addAll(handlers); return super.addRoute(method, path.toString(), handler, diff --git a/packages/framework/lib/src/core/server.dart b/packages/framework/lib/src/core/server.dart index 6ddd2ed5..52c9e157 100644 --- a/packages/framework/lib/src/core/server.dart +++ b/packages/framework/lib/src/core/server.dart @@ -26,21 +26,21 @@ typedef FutureOr AngelConfigurer(Angel app); /// A function that asynchronously generates a view from the given path and data. typedef FutureOr ViewGenerator(String path, - [Map data]); + [Map? data]); /// A powerful real-time/REST/MVC server class. class Angel extends Routable { static ViewGenerator noViewEngineConfigured = - (String view, [Map data]) => 'No view engine has been configured yet.'; + (String view, [Map? data]) => 'No view engine has been configured yet.'; final List _children = []; final Map< String, - Tuple4, ParseResult, + Tuple4, ParseResult?, MiddlewarePipeline>> handlerCache = HashMap(); - Router _flattened; - Angel _parent; + Router? _flattened; + Angel? _parent; /// A global Map of converters that can transform responses bodies. final Map, List>> encoders = {}; @@ -51,7 +51,7 @@ class Angel extends Routable { final MimeTypeResolver mimeTypeResolver = MimeTypeResolver(); /// A middleware to inject a serialize on every request. - FutureOr Function(dynamic) serializer; + FutureOr Function(dynamic)? serializer; /// A [Map] of dependency data obtained via reflection. /// @@ -59,7 +59,7 @@ class Angel extends Routable { Map get preContained => _preContained; /// Returns the [flatten]ed version of this router in production. - Router get optimizedRouter => _flattened ?? this; + Router get optimizedRouter => _flattened ?? this; /// Determines whether to allow HTTP request method overrides. bool allowMethodOverrides = true; @@ -67,10 +67,10 @@ class Angel extends Routable { /// All child application mounted on this instance. List get children => List.unmodifiable(_children); - final Map _controllers = {}; + final Map _controllers = {}; /// A set of [Controller] objects that have been loaded into the application. - Map get controllers => _controllers; + Map get controllers => _controllers; /// Now *deprecated*, in favor of [AngelEnv] and [angelEnv]. Use `app.environment.isProduction` /// instead. @@ -91,10 +91,10 @@ class Angel extends Routable { final AngelEnvironment environment; /// Returns the parent instance of this application, if any. - Angel get parent => _parent; + Angel? get parent => _parent; /// Outputs diagnostics and debug messages. - Logger logger; + Logger? logger; /// Plug-ins to be called right before server startup. /// @@ -121,7 +121,7 @@ class Angel extends Routable { /// A function that renders views. /// /// Called by [ResponseContext]@`render`. - ViewGenerator viewGenerator = noViewEngineConfigured; + ViewGenerator? viewGenerator = noViewEngineConfigured; /// The handler currently configured to run on [AngelHttpException]s. Function(AngelHttpException e, RequestContext req, ResponseContext res) @@ -148,9 +148,9 @@ class Angel extends Routable { }; @override - Route addRoute( - String method, String path, RequestHandler handler, - {Iterable middleware}) { + Route addRoute( + String? method, String? path, RequestHandler? handler, + {Iterable? middleware}) { middleware ??= []; if (_flattened != null) { logger?.warning( @@ -163,7 +163,7 @@ class Angel extends Routable { } @override - mount(String path, Router router) { + mount(String path, Router router) { if (_flattened != null) { logger?.warning( 'WARNING: You added mounted a child router ($path) on the router, after it had been optimized.'); @@ -182,12 +182,12 @@ class Angel extends Routable { /// Loads some base dependencies into the service container. void bootstrapContainer() { if (runtimeType != Angel) { - container.registerSingleton(this); + container!.registerSingleton(this); } - container.registerSingleton(this); - container.registerSingleton(this); - container.registerSingleton(this); + container!.registerSingleton(this); + container!.registerSingleton(this); + container!.registerSingleton(this); } /// Shuts down the server, and closes any open [StreamController]s. @@ -216,14 +216,14 @@ class Angel extends Routable { @override void dumpTree( - {callback(String tree), + {callback(String tree)?, String header = 'Dumping route tree:', String tab = ' ', bool showMatchers = false}) { if (environment.isProduction) { _flattened ??= flatten(this); - _flattened.dumpTree( + _flattened!.dumpTree( callback: callback, header: header?.isNotEmpty == true ? header @@ -284,7 +284,7 @@ class Angel extends Routable { /// Attempts to find a property by the given name within this application. findProperty(key) { if (configuration.containsKey(key)) return configuration[key]; - return parent != null ? parent.findProperty(key) : null; + return parent != null ? parent!.findProperty(key) : null; } /// Runs several optimizations, *if* [angelEnv.isProduction] is `true`. @@ -305,7 +305,7 @@ class Angel extends Routable { /// If this function has been reflected before, then /// the execution will be faster, as the injection requirements were stored beforehand. Future runContained(Function handler, RequestContext req, ResponseContext res, - [Container container]) { + [Container? container]) { return Future.sync(() { if (_preContained.containsKey(handler)) { return handleContained(handler, _preContained[handler], container)( @@ -318,11 +318,11 @@ class Angel extends Routable { /// Runs with DI, and *always* reflects. Prefer [runContained]. Future runReflected(Function handler, RequestContext req, ResponseContext res, - [Container container]) { + [Container? container]) { container ??= req?.container ?? res?.app?.container; var h = handleContained( handler, - _preContained[handler] = preInject(handler, container.reflector), + _preContained[handler] = preInject(handler, container!.reflector), container); return Future.sync(() => h(req, res)); // return closureMirror.apply(args).reflectee; @@ -340,13 +340,13 @@ class Angel extends Routable { /// provide a [type] argument as well. /// /// If you are on `Dart >=2.0.0`, simply call `mountController()`. - Future mountController([Type type]) { - var controller = container.make(type); + Future mountController([Type? type]) { + T controller = container!.make(type)!; return configure(controller.configureServer).then((_) => controller); } /// Shorthand for calling `all('*', handler)`. - Route fallback(RequestHandler handler) { + Route fallback(RequestHandler handler) { return all('*', handler); } diff --git a/packages/framework/lib/src/core/service.dart b/packages/framework/lib/src/core/service.dart index fdde7586..16ea434d 100644 --- a/packages/framework/lib/src/core/service.dart +++ b/packages/framework/lib/src/core/service.dart @@ -68,29 +68,31 @@ class Service extends Routable { List get bootstrappers => []; /// The [Angel] app powering this service. - Angel app; + Angel? app; /// Closes this service, including any database connections or stream controllers. void close() {} /// An optional [readData] function can be passed to handle non-map/non-json bodies. - Service({FutureOr Function(RequestContext, ResponseContext) readData}) { - _readData = readData ?? - (req, res) { - if (req.bodyAsObject is! Data) { - throw AngelHttpException.badRequest( - message: - 'Invalid request body. Expected $Data; found ${req.bodyAsObject} instead.'); - } else { - return req.bodyAsObject as Data; - } - }; + Service( + {FutureOr Function(RequestContext, ResponseContext?)? readData}) { + _readData = readData; + + _readData ??= (req, res) { + if (req.bodyAsObject is! Data) { + throw AngelHttpException.badRequest( + message: + 'Invalid request body. Expected $Data; found ${req.bodyAsObject} instead.'); + } else { + return req.bodyAsObject as Data?; + } + }; } - FutureOr Function(RequestContext, ResponseContext) _readData; + FutureOr? Function(RequestContext, ResponseContext?)? _readData; /// A [Function] that reads the request body and converts it into [Data]. - FutureOr Function(RequestContext, ResponseContext) get readData => + FutureOr? Function(RequestContext, ResponseContext?)? get readData => _readData; /// Retrieves the first object from the result of calling [index] with the given [params]. @@ -103,7 +105,7 @@ class Service extends Routable { /// /// A custom [errorMessage] may be provided. Future findOne( - [Map params, + [Map? params, String errorMessage = 'No record was found matching the given query.']) { return index(params).then((result) { if (result == null) { @@ -119,12 +121,12 @@ class Service extends Routable { } /// Retrieves all resources. - Future> index([Map params]) { + Future> index([Map? params]) { throw AngelHttpException.methodNotAllowed(); } /// Retrieves the desired resource. - Future read(Id id, [Map params]) { + Future read(Id? id, [Map? params]) { throw AngelHttpException.methodNotAllowed(); } @@ -132,27 +134,27 @@ class Service extends Routable { /// /// Service implementations should override this to ensure data is fetched within a /// single round trip. - Future> readMany(List ids, [Map params]) { + Future> readMany(List ids, [Map? params]) { return Future.wait(ids.map((id) => read(id, params))); } /// Creates a resource. - Future create(Data data, [Map params]) { + Future create(Data data, [Map? params]) { throw AngelHttpException.methodNotAllowed(); } /// Modifies a resource. - Future modify(Id id, Data data, [Map params]) { + Future modify(Id? id, Data data, [Map? params]) { throw AngelHttpException.methodNotAllowed(); } /// Overwrites a resource. - Future update(Id id, Data data, [Map params]) { + Future update(Id? id, Data data, [Map? params]) { throw AngelHttpException.methodNotAllowed(); } /// Removes the given resource. - Future remove(Id id, [Map params]) { + Future remove(Id? id, [Map? params]) { throw AngelHttpException.methodNotAllowed(); } @@ -161,14 +163,15 @@ class Service extends Routable { /// /// Handy utility for handling data in a type-safe manner. Service map(U Function(Data) encoder, Data Function(U) decoder, - {FutureOr Function(RequestContext, ResponseContext) readData}) { + {FutureOr Function(RequestContext, ResponseContext)? readData}) { readData ??= (req, res) async { - var inner = await this.readData(req, res); + Data inner = await this.readData!(req, res)!; return encoder(inner); }; return AnonymousService( - readData: readData, + readData: readData as FutureOr Function( + RequestContext, ResponseContext?)?, index: ([params]) { return index(params).then((it) => it.map(encoder).toList()); }, @@ -195,7 +198,7 @@ class Service extends Routable { /// The single type argument, [T], is used to determine how to parse the [id]. /// /// For example, `parseId` attempts to parse the value as a [bool]. - static T parseId(id) { + static T? parseId(id) { if (id == 'null' || id == null) { return null; } else if (T == String) { @@ -214,7 +217,7 @@ class Service extends Routable { } /// Generates RESTful routes pointing to this class's methods. - void addRoutes([Service service]) { + void addRoutes([Service? service]) { _addRoutesInner(service ?? this, bootstrappers); } @@ -223,13 +226,13 @@ class Service extends Routable { var handlers = List.from(handlerss); // Add global middleware if declared on the instance itself - Middleware before = - getAnnotation(service, app.container.reflector); + Middleware? before = + getAnnotation(service, app!.container!.reflector); if (before != null) handlers.addAll(before.handlers); - Middleware indexMiddleware = - getAnnotation(service.index, app.container.reflector); + Middleware? indexMiddleware = + getAnnotation(service.index, app!.container!.reflector); get('/', (req, res) { return this.index(mergeMap([ {'query': req.queryParameters}, @@ -237,17 +240,19 @@ class Service extends Routable { req.serviceParams ])); }, - middleware: [] + middleware: [] ..addAll(handlers) - ..addAll((indexMiddleware == null) ? [] : indexMiddleware.handlers)); + ..addAll((indexMiddleware == null) + ? [] + : indexMiddleware.handlers.toList())); - Middleware createMiddleware = - getAnnotation(service.create, app.container.reflector); + Middleware? createMiddleware = + getAnnotation(service.create, app!.container!.reflector); post('/', (req, ResponseContext res) { return req.parseBody().then((_) async { return await this .create( - await readData(req, res), + (await readData!(req, res))!, mergeMap([ {'query': req.queryParameters}, restProvider, @@ -261,11 +266,12 @@ class Service extends Routable { }, middleware: [] ..addAll(handlers) - ..addAll( - (createMiddleware == null) ? [] : createMiddleware.handlers)); + ..addAll((createMiddleware == null) + ? [] + : createMiddleware.handlers.toList())); - Middleware readMiddleware = - getAnnotation(service.read, app.container.reflector); + Middleware? readMiddleware = + getAnnotation(service.read, app!.container!.reflector); get('/:id', (req, res) { return this.read( @@ -278,15 +284,18 @@ class Service extends Routable { }, middleware: [] ..addAll(handlers) - ..addAll((readMiddleware == null) ? [] : readMiddleware.handlers)); + ..addAll((readMiddleware == null) + ? [] + : readMiddleware.handlers.toList())); + + Middleware? modifyMiddleware = + getAnnotation(service.modify, app!.container!.reflector); - Middleware modifyMiddleware = - getAnnotation(service.modify, app.container.reflector); patch('/:id', (req, res) { return req.parseBody().then((_) async { return await this.modify( parseId(req.params['id']), - await readData(req, res), + (await readData!(req, res))!, mergeMap([ {'query': req.queryParameters}, restProvider, @@ -296,16 +305,17 @@ class Service extends Routable { }, middleware: [] ..addAll(handlers) - ..addAll( - (modifyMiddleware == null) ? [] : modifyMiddleware.handlers)); + ..addAll((modifyMiddleware == null) + ? [] + : modifyMiddleware.handlers.toList())); - Middleware updateMiddleware = - getAnnotation(service.update, app.container.reflector); + Middleware? updateMiddleware = + getAnnotation(service.update, app!.container!.reflector); post('/:id', (req, res) { return req.parseBody().then((_) async { return await this.update( parseId(req.params['id']), - await readData(req, res), + (await readData!(req, res))!, mergeMap([ {'query': req.queryParameters}, restProvider, @@ -315,13 +325,15 @@ class Service extends Routable { }, middleware: [] ..addAll(handlers) - ..addAll( - (updateMiddleware == null) ? [] : updateMiddleware.handlers)); + ..addAll((updateMiddleware == null) + ? [] + : updateMiddleware.handlers.toList())); + put('/:id', (req, res) { return req.parseBody().then((_) async { return await this.update( parseId(req.params['id']), - await readData(req, res), + (await readData!(req, res))!, mergeMap([ {'query': req.queryParameters}, restProvider, @@ -331,11 +343,12 @@ class Service extends Routable { }, middleware: [] ..addAll(handlers) - ..addAll( - (updateMiddleware == null) ? [] : updateMiddleware.handlers)); + ..addAll((updateMiddleware == null) + ? [] + : updateMiddleware.handlers.toList())); - Middleware removeMiddleware = - getAnnotation(service.remove, app.container.reflector); + Middleware? removeMiddleware = + getAnnotation(service.remove, app!.container!.reflector); delete('/', (req, res) { return this.remove( null, @@ -347,8 +360,10 @@ class Service extends Routable { }, middleware: [] ..addAll(handlers) - ..addAll( - (removeMiddleware == null) ? [] : removeMiddleware.handlers)); + ..addAll((removeMiddleware == null) + ? [] + : removeMiddleware.handlers.toList())); + delete('/:id', (req, res) { return this.remove( parseId(req.params['id']), @@ -360,8 +375,9 @@ class Service extends Routable { }, middleware: [] ..addAll(handlers) - ..addAll( - (removeMiddleware == null) ? [] : removeMiddleware.handlers)); + ..addAll((removeMiddleware == null) + ? [] + : removeMiddleware.handlers.toList())); // REST compliance put('/', (req, res) => throw AngelHttpException.notFound()); diff --git a/packages/framework/lib/src/http/angel_http.dart b/packages/framework/lib/src/http/angel_http.dart index bf77bfc8..0276f190 100644 --- a/packages/framework/lib/src/http/angel_http.dart +++ b/packages/framework/lib/src/http/angel_http.dart @@ -21,13 +21,13 @@ class AngelHttp extends Driver server == null ? Uri() - : Uri(scheme: 'http', host: server.address.address, port: server.port); + : Uri(scheme: 'http', host: server!.address.address, port: server!.port); - AngelHttp._(Angel app, + AngelHttp._(Angel? app, Future Function(dynamic, int) serverGenerator, bool useZone) : super(app, serverGenerator, useZone: useZone); - factory AngelHttp(Angel app, {bool useZone = true}) { + factory AngelHttp(Angel? app, {bool useZone = true}) { return AngelHttp._(app, HttpServer.bind, useZone); } @@ -52,7 +52,7 @@ class AngelHttp extends Driver server; + HttpServer? get httpServer => server; Future handleRequest(HttpRequest request) => handleRawRequest(request, request.response); @@ -87,17 +87,27 @@ class AngelHttp extends Driver createResponseContext( HttpRequest request, HttpResponse response, - [HttpRequestContext correspondingRequest]) { - return Future.value( - HttpResponseContext(response, app, correspondingRequest) - ..serializer = (app.serializer ?? json.encode) - ..encoders.addAll(app.encoders ?? {})); + [HttpRequestContext? correspondingRequest]) { + // TODO: Refactored to overcome NNBD migration error + HttpResponseContext context = + HttpResponseContext(response, app, correspondingRequest); + + if (app!.serializer == null) { + context.serializer = json.encode; + } else { + context.serializer = app!.serializer; + } + return Future.value(context); +// return Future.value( +// HttpResponseContext(response, app, correspondingRequest) +// ..serializer = (app.serializer ?? json.encode) +// ..encoders.addAll(app.encoders ?? {})); } @override @@ -114,8 +124,8 @@ class AngelHttp extends Driver - response.headers.set(key, value); + void setHeader(HttpResponse response, String key, String? value) => + response.headers.set(key, value!); @override void setStatusCode(HttpResponse response, int value) => diff --git a/packages/framework/lib/src/http/http_request_context.dart b/packages/framework/lib/src/http/http_request_context.dart index 42538ce1..ec1afc99 100644 --- a/packages/framework/lib/src/http/http_request_context.dart +++ b/packages/framework/lib/src/http/http_request_context.dart @@ -7,40 +7,40 @@ import 'package:http_parser/http_parser.dart'; import '../core/core.dart'; /// An implementation of [RequestContext] that wraps a [HttpRequest]. -class HttpRequestContext extends RequestContext { - Container _container; - MediaType _contentType; - HttpRequest _io; - String _override, _path; +class HttpRequestContext extends RequestContext { + Container? _container; + MediaType? _contentType; + HttpRequest? _io; + String? _override, _path; @override - Container get container => _container; + Container? get container => _container; @override - MediaType get contentType { + MediaType? get contentType { return _contentType; } @override List get cookies { - return rawRequest.cookies; + return rawRequest!.cookies; } @override HttpHeaders get headers { - return rawRequest.headers; + return rawRequest!.headers; } @override - String get hostname { - return rawRequest.headers.value('host'); + String? get hostname { + return rawRequest!.headers.value('host'); } /// The underlying [HttpRequest] instance underneath this context. - HttpRequest get rawRequest => _io; + HttpRequest? get rawRequest => _io; @override - Stream> get body => _io; + Stream>? get body => _io; @override String get method { @@ -49,34 +49,34 @@ class HttpRequestContext extends RequestContext { @override String get originalMethod { - return rawRequest.method; + return rawRequest!.method; } @override - String get path { + String? get path { return _path; } @override InternetAddress get remoteAddress { - return rawRequest.connectionInfo.remoteAddress; + return rawRequest!.connectionInfo!.remoteAddress; } @override HttpSession get session { - return rawRequest.session; + return rawRequest!.session; } @override Uri get uri { - return rawRequest.uri; + return rawRequest!.uri; } /// Magically transforms an [HttpRequest] into a [RequestContext]. static Future from( HttpRequest request, Angel app, String path) { HttpRequestContext ctx = HttpRequestContext() - .._container = app.container.createChild(); + .._container = app.container!.createChild(); String override = request.method; diff --git a/packages/framework/lib/src/http/http_response_context.dart b/packages/framework/lib/src/http/http_response_context.dart index 8903d245..5ddc8cb8 100644 --- a/packages/framework/lib/src/http/http_response_context.dart +++ b/packages/framework/lib/src/http/http_response_context.dart @@ -12,11 +12,11 @@ class HttpResponseContext extends ResponseContext { /// The underlying [HttpResponse] under this instance. @override final HttpResponse rawResponse; - Angel app; + Angel? app; - LockableBytesBuilder _buffer; + LockableBytesBuilder? _buffer; - final HttpRequestContext _correspondingRequest; + final HttpRequestContext? _correspondingRequest; bool _isDetached = false, _isClosed = false, _streamInitialized = false; HttpResponseContext(this.rawResponse, this.app, [this._correspondingRequest]); @@ -28,7 +28,7 @@ class HttpResponseContext extends ResponseContext { } @override - RequestContext get correspondingRequest { + RequestContext? get correspondingRequest { return _correspondingRequest; } @@ -41,10 +41,10 @@ class HttpResponseContext extends ResponseContext { bool get isBuffered => _buffer != null; @override - BytesBuilder get buffer => _buffer; + BytesBuilder? get buffer => _buffer; @override - void addError(Object error, [StackTrace stackTrace]) { + void addError(Object error, [StackTrace? stackTrace]) { rawResponse.addError(error, stackTrace); super.addError(error, stackTrace); } @@ -54,10 +54,10 @@ class HttpResponseContext extends ResponseContext { _buffer = LockableBytesBuilder(); } - Iterable __allowedEncodings; + Iterable? __allowedEncodings; - Iterable get _allowedEncodings { - return __allowedEncodings ??= correspondingRequest.headers + Iterable? get _allowedEncodings { + return __allowedEncodings ??= correspondingRequest!.headers! .value('accept-encoding') ?.split(',') ?.map((s) => s.trim()) @@ -89,7 +89,7 @@ class HttpResponseContext extends ResponseContext { headers.forEach(rawResponse.headers.set); if (headers.containsKey('content-length')) { - rawResponse.contentLength = int.tryParse(headers['content-length']) ?? + rawResponse.contentLength = int.tryParse(headers['content-length']!) ?? rawResponse.contentLength; } @@ -100,8 +100,8 @@ class HttpResponseContext extends ResponseContext { if (encoders.isNotEmpty && correspondingRequest != null) { if (_allowedEncodings != null) { - for (var encodingName in _allowedEncodings) { - Converter, List> encoder; + for (var encodingName in _allowedEncodings!) { + Converter, List>? encoder; String key = encodingName; if (encoders.containsKey(encodingName)) { @@ -134,8 +134,8 @@ class HttpResponseContext extends ResponseContext { if (encoders.isNotEmpty && correspondingRequest != null) { if (_allowedEncodings != null) { - for (var encodingName in _allowedEncodings) { - Converter, List> encoder; + for (var encodingName in _allowedEncodings!) { + Converter, List>? encoder; String key = encodingName; if (encoders.containsKey(encodingName)) { @@ -145,7 +145,7 @@ class HttpResponseContext extends ResponseContext { } if (encoder != null) { - output = encoders[key].bind(output); + output = encoders[key]!.bind(output); break; } } @@ -165,8 +165,8 @@ class HttpResponseContext extends ResponseContext { if (encoders.isNotEmpty && correspondingRequest != null) { if (_allowedEncodings != null) { - for (var encodingName in _allowedEncodings) { - Converter, List> encoder; + for (var encodingName in _allowedEncodings!) { + Converter, List>? encoder; String key = encodingName; if (encoders.containsKey(encodingName)) { @@ -176,7 +176,7 @@ class HttpResponseContext extends ResponseContext { } if (encoder != null) { - data = encoders[key].convert(data); + data = encoders[key]!.convert(data); break; } } @@ -186,7 +186,7 @@ class HttpResponseContext extends ResponseContext { rawResponse.add(data); } } else { - buffer.add(data); + buffer!.add(data); } } @@ -203,7 +203,7 @@ class HttpResponseContext extends ResponseContext { // this try/catch prevents a crash. } } else { - _buffer.lock(); + _buffer!.lock(); } _isClosed = true; diff --git a/packages/framework/lib/src/http2/angel_http2.dart b/packages/framework/lib/src/http2/angel_http2.dart index 94ed6a66..edd7f515 100644 --- a/packages/framework/lib/src/http2/angel_http2.dart +++ b/packages/framework/lib/src/http2/angel_http2.dart @@ -18,14 +18,14 @@ Future startSharedHttp2( /// Adapts `package:http2`'s [ServerTransportConnection] to serve Angel. class AngelHttp2 extends Driver { - final ServerSettings settings; - AngelHttp _http; + final ServerSettings? settings; + late AngelHttp _http; final StreamController _onHttp1 = StreamController(); final Map _sessions = {}; final Uuid _uuid = Uuid(); - _AngelHttp2ServerSocket _artificial; + _AngelHttp2ServerSocket? _artificial; - SecureServerSocket get socket => _artificial; + SecureServerSocket? get socket => _artificial; AngelHttp2._( Angel app, @@ -45,7 +45,7 @@ class AngelHttp2 extends Driver get onHttp1 => _onHttp1.stream; @override - Future generateServer([address, int port]) async { - var s = await serverGenerator(address ?? '127.0.0.1', port ?? 0); + Future generateServer([address, int? port]) async { + SecureServerSocket s = await serverGenerator(address ?? '127.0.0.1', port ?? 0); return _artificial = _AngelHttp2ServerSocket(s, this); } @override Future close() async { - await _artificial.close(); + await _artificial!.close(); await _http?.close(); return await super.close(); } @@ -98,15 +98,15 @@ class AngelHttp2 extends Driver createRequestContext( Socket request, ServerTransportStream response) { - return Http2RequestContext.from(response, request, app, _sessions, _uuid); + return Http2RequestContext.from(response, request, app!, _sessions, _uuid); } @override Future createResponseContext( Socket request, ServerTransportStream response, - [Http2RequestContext correspondingRequest]) async { + [Http2RequestContext? correspondingRequest]) async { return Http2ResponseContext(app, response, correspondingRequest) - ..encoders.addAll(app.encoders); + ..encoders.addAll(app!.encoders); } @override @@ -128,8 +128,8 @@ class AngelHttp2 extends Driver Uri( scheme: 'https', - host: server.address.address, - port: server.port != 443 ? server.port : null); + host: server!.address.address, + port: server!.port != 443 ? server!.port : null); @override void writeStringToResponse(ServerTransportStream response, String value) { @@ -173,8 +173,8 @@ class _FakeServerSocket extends Stream implements ServerSocket { int get port => angel.port; @override - StreamSubscription listen(void Function(Socket event) onData, - {Function onError, void Function() onDone, bool cancelOnError}) { + StreamSubscription listen(void Function(Socket event)? onData, + {Function? onError, void Function()? onDone, bool? cancelOnError}) { return _ctrl.stream.listen(onData, cancelOnError: cancelOnError, onError: onError, onDone: onDone); } @@ -185,8 +185,8 @@ class _AngelHttp2ServerSocket extends Stream final SecureServerSocket socket; final AngelHttp2 driver; final _ctrl = StreamController(); - _FakeServerSocket _fake; - StreamSubscription _sub; + late _FakeServerSocket _fake; + StreamSubscription? _sub; _AngelHttp2ServerSocket(this.socket, this.driver) { _fake = _FakeServerSocket(this); @@ -208,7 +208,7 @@ class _AngelHttp2ServerSocket extends Stream }, onDone: _ctrl.close, onError: (e, st) { - driver.app.logger.warning( + driver.app!.logger!.warning( 'HTTP/2 incoming connection failure: ', e, st as StackTrace); }, ); @@ -227,10 +227,10 @@ class _AngelHttp2ServerSocket extends Stream @override StreamSubscription listen( - void Function(SecureSocket event) onData, - {Function onError, - void Function() onDone, - bool cancelOnError}) { + void Function(SecureSocket event)? onData, + {Function? onError, + void Function()? onDone, + bool? cancelOnError}) { return _ctrl.stream.listen(onData, cancelOnError: cancelOnError, onError: onError, onDone: onDone); } diff --git a/packages/framework/lib/src/http2/http2_request_context.dart b/packages/framework/lib/src/http2/http2_request_context.dart index de4953d8..8bbb7678 100644 --- a/packages/framework/lib/src/http2/http2_request_context.dart +++ b/packages/framework/lib/src/http2/http2_request_context.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:angel_container/src/container.dart'; import 'package:angel_framework/angel_framework.dart'; +import 'package:collection/collection.dart' show IterableExtension; import 'package:http2/transport.dart'; import 'package:mock_request/mock_request.dart'; import 'package:uuid/uuid.dart'; @@ -10,16 +11,16 @@ import 'package:uuid/uuid.dart'; final RegExp _comma = RegExp(r',\s*'); final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)'); -class Http2RequestContext extends RequestContext { +class Http2RequestContext extends RequestContext { final StreamController> _body = StreamController(); final Container container; - List _cookies; - HttpHeaders _headers; - String _method, _override, _path; - HttpSession _session; - Socket _socket; - ServerTransportStream _stream; - Uri _uri; + List? _cookies; + HttpHeaders? _headers; + String? _method, _override, _path; + HttpSession? _session; + late Socket _socket; + ServerTransportStream? _stream; + Uri? _uri; Http2RequestContext._(this.container); @@ -33,7 +34,7 @@ class Http2RequestContext extends RequestContext { Map sessions, Uuid uuid) { var c = Completer(); - var req = Http2RequestContext._(app.container.createChild()) + var req = Http2RequestContext._(app.container!.createChild()) ..app = app .._socket = socket .._stream = stream; @@ -124,7 +125,7 @@ class Http2RequestContext extends RequestContext { // Apply session var dartSessId = - cookies.firstWhere((c) => c.name == 'DARTSESSID', orElse: () => null); + cookies.firstWhereOrNull((c) => c.name == 'DARTSESSID'); if (dartSessId == null) { dartSessId = Cookie('DARTSESSID', uuid.v4()); @@ -132,23 +133,23 @@ class Http2RequestContext extends RequestContext { req._session = sessions.putIfAbsent( dartSessId.value, - () => MockHttpSession(id: dartSessId.value), + () => MockHttpSession(id: dartSessId!.value), ); return c.future; } @override - List get cookies => _cookies; + List? get cookies => _cookies; /// The underlying HTTP/2 [ServerTransportStream]. - ServerTransportStream get stream => _stream; + ServerTransportStream? get stream => _stream; @override - Uri get uri => _uri; + Uri? get uri => _uri; @override - HttpSession get session { + HttpSession? get session { return _session; } @@ -156,25 +157,25 @@ class Http2RequestContext extends RequestContext { InternetAddress get remoteAddress => _socket.remoteAddress; @override - String get path { + String? get path { return _path; } @override - String get originalMethod { + String? get originalMethod { return _method; } @override - String get method { + String? get method { return _override ?? _method; } @override - String get hostname => _headers.value('host'); + String? get hostname => _headers!.value('host'); @override - HttpHeaders get headers => _headers; + HttpHeaders? get headers => _headers; @override Future close() { @@ -183,5 +184,5 @@ class Http2RequestContext extends RequestContext { } @override - ServerTransportStream get rawRequest => _stream; + ServerTransportStream? get rawRequest => _stream; } diff --git a/packages/framework/lib/src/http2/http2_response_context.dart b/packages/framework/lib/src/http2/http2_response_context.dart index 55fb6d45..4014bd1b 100644 --- a/packages/framework/lib/src/http2/http2_response_context.dart +++ b/packages/framework/lib/src/http2/http2_response_context.dart @@ -6,24 +6,24 @@ import 'package:http2/transport.dart'; import 'http2_request_context.dart'; class Http2ResponseContext extends ResponseContext { - final Angel app; + final Angel? app; final ServerTransportStream stream; ServerTransportStream get rawResponse => stream; - LockableBytesBuilder _buffer; + LockableBytesBuilder? _buffer; - final Http2RequestContext _req; + final Http2RequestContext? _req; bool _isDetached = false, _isClosed = false, _streamInitialized = false, _isPush = false; - Uri _targetUri; + Uri? _targetUri; Http2ResponseContext(this.app, this.stream, this._req) { - _targetUri = _req.uri; + _targetUri = _req!.uri; } final List _pushes = []; @@ -43,9 +43,9 @@ class Http2ResponseContext extends ResponseContext { } @override - RequestContext get correspondingRequest => _req; + RequestContext? get correspondingRequest => _req; - Uri get targetUri => _targetUri; + Uri? get targetUri => _targetUri; @override bool get isOpen { @@ -56,10 +56,10 @@ class Http2ResponseContext extends ResponseContext { bool get isBuffered => _buffer != null; @override - BytesBuilder get buffer => _buffer; + BytesBuilder? get buffer => _buffer; @override - void addError(Object error, [StackTrace stackTrace]) { + void addError(Object error, [StackTrace? stackTrace]) { super.addError(error, stackTrace); } @@ -78,8 +78,8 @@ class Http2ResponseContext extends ResponseContext { if (encoders.isNotEmpty && correspondingRequest != null) { if (_allowedEncodings != null) { - for (var encodingName in _allowedEncodings) { - Converter, List> encoder; + for (var encodingName in _allowedEncodings!) { + Converter, List>? encoder; String key = encodingName; if (encoders.containsKey(encodingName)) { @@ -98,11 +98,11 @@ class Http2ResponseContext extends ResponseContext { // Add all normal headers for (var key in this.headers.keys) { - headers.add(Header.ascii(key.toLowerCase(), this.headers[key])); + headers.add(Header.ascii(key.toLowerCase(), this.headers[key]!)); } // Persist session ID - cookies.add(Cookie('DARTSESSID', _req.session.id)); + cookies.add(Cookie('DARTSESSID', _req!.session!.id)); // Send all cookies for (var cookie in cookies) { @@ -113,10 +113,10 @@ class Http2ResponseContext extends ResponseContext { return _streamInitialized = true; } - Iterable __allowedEncodings; + Iterable? __allowedEncodings; - Iterable get _allowedEncodings { - return __allowedEncodings ??= correspondingRequest.headers + Iterable? get _allowedEncodings { + return __allowedEncodings ??= correspondingRequest!.headers! .value('accept-encoding') ?.split(',') ?.map((s) => s.trim()) @@ -138,8 +138,8 @@ class Http2ResponseContext extends ResponseContext { if (encoders.isNotEmpty && correspondingRequest != null) { if (_allowedEncodings != null) { - for (var encodingName in _allowedEncodings) { - Converter, List> encoder; + for (var encodingName in _allowedEncodings!) { + Converter, List>? encoder; String key = encodingName; if (encoders.containsKey(encodingName)) { @@ -149,7 +149,7 @@ class Http2ResponseContext extends ResponseContext { } if (encoder != null) { - output = encoders[key].bind(output); + output = encoders[key]!.bind(output); break; } } @@ -169,8 +169,8 @@ class Http2ResponseContext extends ResponseContext { if (!_isClosed) { if (encoders.isNotEmpty && correspondingRequest != null) { if (_allowedEncodings != null) { - for (var encodingName in _allowedEncodings) { - Converter, List> encoder; + for (var encodingName in _allowedEncodings!) { + Converter, List>? encoder; String key = encodingName; if (encoders.containsKey(encodingName)) { @@ -180,7 +180,7 @@ class Http2ResponseContext extends ResponseContext { } if (encoder != null) { - data = encoders[key].convert(data); + data = encoders[key]!.convert(data); break; } } @@ -190,7 +190,7 @@ class Http2ResponseContext extends ResponseContext { stream.sendData(data); } } else { - buffer.add(data); + buffer!.add(data); } } @@ -208,7 +208,7 @@ class Http2ResponseContext extends ResponseContext { /// Pushes a resource to the client. Http2ResponseContext push(String path, {Map headers = const {}, String method = 'GET'}) { - var targetUri = _req.uri.replace(path: path); + var targetUri = _req!.uri!.replace(path: path); var h =
[ Header.ascii(':authority', targetUri.authority), @@ -218,7 +218,7 @@ class Http2ResponseContext extends ResponseContext { ]; for (var key in headers.keys) { - h.add(Header.ascii(key, headers[key])); + h.add(Header.ascii(key, headers[key]!)); } var s = stream.push(h); diff --git a/packages/framework/lib/src/safe_stream_controller.dart b/packages/framework/lib/src/safe_stream_controller.dart index bd2de680..70837bde 100644 --- a/packages/framework/lib/src/safe_stream_controller.dart +++ b/packages/framework/lib/src/safe_stream_controller.dart @@ -12,7 +12,7 @@ abstract class SafeCtrl { void add(T event); - void addError(error, [StackTrace stackTrace]); + void addError(error, [StackTrace? stackTrace]); Future close(); @@ -20,16 +20,16 @@ abstract class SafeCtrl { } class _SingleSafeCtrl implements SafeCtrl { - StreamController _stream; + late StreamController _stream; bool _hasListener = false, _initialized = false; - _InitCallback _initializer; + _InitCallback? _initializer; _SingleSafeCtrl() { _stream = StreamController(onListen: () { _hasListener = true; if (!_initialized && _initializer != null) { - _initializer(); + _initializer!(); _initialized = true; } }, onPause: () { @@ -50,8 +50,8 @@ class _SingleSafeCtrl implements SafeCtrl { } @override - void addError(error, [StackTrace stackTrace]) { - if (_hasListener) _stream.addError(error, stackTrace); + void addError(error, [StackTrace? stackTrace]) { + if (_hasListener) _stream.addError(error as Object, stackTrace); } @override @@ -65,7 +65,7 @@ class _SingleSafeCtrl implements SafeCtrl { if (!_hasListener) { _initializer = callback; } else { - _initializer(); + _initializer!(); _initialized = true; } } @@ -73,17 +73,17 @@ class _SingleSafeCtrl implements SafeCtrl { } class _BroadcastSafeCtrl implements SafeCtrl { - StreamController _stream; + late StreamController _stream; int _listeners = 0; bool _initialized = false; - _InitCallback _initializer; + _InitCallback? _initializer; _BroadcastSafeCtrl() { _stream = StreamController.broadcast(onListen: () { _listeners++; if (!_initialized && _initializer != null) { - _initializer(); + _initializer!(); _initialized = true; } }, onCancel: () { @@ -100,8 +100,8 @@ class _BroadcastSafeCtrl implements SafeCtrl { } @override - void addError(error, [StackTrace stackTrace]) { - if (_listeners > 0) _stream.addError(error, stackTrace); + void addError(error, [StackTrace? stackTrace]) { + if (_listeners > 0) _stream.addError(error as Object, stackTrace); } @override @@ -115,7 +115,7 @@ class _BroadcastSafeCtrl implements SafeCtrl { if (_listeners <= 0) { _initializer = callback; } else { - _initializer(); + _initializer!(); _initialized = true; } } diff --git a/packages/framework/lib/src/util.dart b/packages/framework/lib/src/util.dart index bda41d9e..99ba9db5 100644 --- a/packages/framework/lib/src/util.dart +++ b/packages/framework/lib/src/util.dart @@ -2,25 +2,25 @@ import 'package:angel_container/angel_container.dart'; final RegExp straySlashes = RegExp(r'(^/+)|(/+$)'); -T matchingAnnotation(List metadata) { +T? matchingAnnotation(List metadata) { for (ReflectedInstance metaDatum in metadata) { if (metaDatum.type.reflectedType == T) { - return metaDatum.reflectee as T; + return metaDatum.reflectee as T?; } } return null; } -T getAnnotation(obj, Reflector reflector) { +T? getAnnotation(obj, Reflector? reflector) { if (reflector == null) { return null; } else { if (obj is Function) { - var methodMirror = reflector.reflectFunction(obj); + var methodMirror = reflector.reflectFunction(obj)!; return matchingAnnotation(methodMirror.annotations); } else { - var classMirror = reflector.reflectClass(obj.runtimeType as Type); + var classMirror = reflector.reflectClass(obj.runtimeType as Type)!; return matchingAnnotation(classMirror.annotations); } } diff --git a/packages/framework/pubspec.yaml b/packages/framework/pubspec.yaml index 562ede92..95534b4b 100644 --- a/packages/framework/pubspec.yaml +++ b/packages/framework/pubspec.yaml @@ -5,7 +5,7 @@ author: Tobe O homepage: https://github.com/angel-dart/angel_framework publish_to: none environment: - sdk: ">=2.10.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: angel_container: git: @@ -60,6 +60,7 @@ dependencies: string_scanner: ^1.1.0 tuple: ^2.0.0 uuid: ^3.0.1 + collection: ^1.15.0 dev_dependencies: http: ^0.13.1 io: ^1.0.0 diff --git a/packages/framework/test/404_hole_test.dart b/packages/framework/test/404_hole_test.dart index eaaf9702..fe61e456 100644 --- a/packages/framework/test/404_hole_test.dart +++ b/packages/framework/test/404_hole_test.dart @@ -8,9 +8,9 @@ import 'package:test/test.dart'; import 'pretty_log.dart'; void main() { - http.IOClient client; - AngelHttp driver; - Logger logger; + late http.IOClient client; + late AngelHttp driver; + late Logger logger; setUp(() async { client = http.IOClient(); diff --git a/packages/framework/test/accepts_test.dart b/packages/framework/test/accepts_test.dart index 6e670e4c..36981951 100644 --- a/packages/framework/test/accepts_test.dart +++ b/packages/framework/test/accepts_test.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:angel_container/mirrors.dart'; import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/http.dart'; @@ -44,7 +45,7 @@ main() { }); group('disallow null', () { - RequestContext req; + late RequestContext req; setUp(() async { req = await acceptContentTypes(); @@ -58,8 +59,9 @@ main() { Future acceptContentTypes( [Iterable contentTypes = const []]) { - var headerString = contentTypes.isEmpty ? null : contentTypes.join(','); - var rq = MockHttpRequest('GET', ENDPOINT); + var headerString = + contentTypes.isEmpty ? ContentType.html : contentTypes.join(','); + var rq = MockHttpRequest('GET', ENDPOINT, persistentConnection: false); rq.headers.set('accept', headerString); rq.close(); var app = Angel(reflector: MirrorsReflector()); diff --git a/packages/framework/test/anonymous_service_test.dart b/packages/framework/test/anonymous_service_test.dart index 41456a15..033de0a4 100644 --- a/packages/framework/test/anonymous_service_test.dart +++ b/packages/framework/test/anonymous_service_test.dart @@ -3,7 +3,7 @@ import 'package:test/test.dart'; main() { test('custom methods', () async { - var svc = AnonymousService( + var svc = AnonymousService( index: ([p]) async => ['index'], read: (id, [p]) async => 'read', create: (data, [p]) async => 'create', diff --git a/packages/framework/test/body_test.dart b/packages/framework/test/body_test.dart index 45a64ba2..e316a4c5 100644 --- a/packages/framework/test/body_test.dart +++ b/packages/framework/test/body_test.dart @@ -13,9 +13,10 @@ void main() { Future request( {bool asJson = true, bool parse = true, - Map bodyFields, - List bodyList}) async { - var rq = MockHttpRequest('POST', Uri(path: '/')); + Map? bodyFields, + List? bodyList}) async { + var rq = + MockHttpRequest('POST', Uri(path: '/'), persistentConnection: false); if (bodyFields != null) { if (asJson) { @@ -115,16 +116,16 @@ void main() { } class Todo { - String text; - bool completed; + String? text; + bool? completed; Todo({this.text, this.completed}); - static Todo fromMap(Map m) => - Todo(text: m['text'] as String, completed: m['complete'] as bool); + static Todo fromMap(Map? m) => + Todo(text: m!['text'] as String?, completed: m['complete'] as bool?); } -class TodoCodec extends Codec { +class TodoCodec extends Codec { @override Converter get decoder => TodoDecoder(); diff --git a/packages/framework/test/common.dart b/packages/framework/test/common.dart index 84b98550..bc394426 100644 --- a/packages/framework/test/common.dart +++ b/packages/framework/test/common.dart @@ -4,8 +4,8 @@ import 'package:angel_framework/angel_framework.dart'; import 'package:matcher/matcher.dart'; class Todo extends Model { - String text; - String over; + String? text; + String? over; Todo({this.text, this.over}); diff --git a/packages/framework/test/controller_test.dart b/packages/framework/test/controller_test.dart index 450d70a0..a75b998f 100644 --- a/packages/framework/test/controller_test.dart +++ b/packages/framework/test/controller_test.dart @@ -70,39 +70,39 @@ bool bar(RequestContext req, ResponseContext res) { } main() { - Angel app; - TodoController todoController; - NoExposeController noExposeCtrl; - HttpServer server; + Angel? app; + late TodoController todoController; + late NoExposeController noExposeCtrl; + late HttpServer server; http.Client client = http.Client(); - String url; + String? url; setUp(() async { app = Angel(reflector: MirrorsReflector()); - app.get( + app!.get( "/redirect", (req, res) async => res.redirectToAction("TodoController@foo", {"foo": "world"})); // Register as a singleton, just for the purpose of this test - if (!app.container.has()) { - app.container.registerSingleton(todoController = TodoController()); + if (!app!.container!.has()) { + app!.container!.registerSingleton(todoController = TodoController()); } // Using mountController(); - await app.mountController(); + await app!.mountController(); - noExposeCtrl = await app.mountController(); + noExposeCtrl = await app!.mountController(); // Place controller in group. The applyRoutes() call, however, is async. // Until https://github.com/angel-dart/route/issues/28 is closed, // this will need to be done by manually mounting the router. var subRouter = Router(); - await todoController.applyRoutes(subRouter, app.container.reflector); - app.mount('/ctrl_group', subRouter); + await todoController.applyRoutes(subRouter, app!.container!.reflector); + app!.mount('/ctrl_group', subRouter); - print(app.controllers); - app.dumpTree(); + print(app!.controllers); + app!.dumpTree(); server = await AngelHttp(app).startServer(); url = 'http://${server.address.address}:${server.port}'; @@ -122,7 +122,7 @@ main() { var app = Angel(reflector: MirrorsReflector()); app.get( '/foo', - ioc(({String bar}) { + ioc(({String? bar}) { return 2; }, optional: ['bar'])); var rq = MockHttpRequest('GET', Uri(path: 'foo')); @@ -171,7 +171,7 @@ main() { group('optional expose', () { test('removes suffixes from controller names', () { - expect(noExposeCtrl.mountPoint.path, 'no_expose'); + expect(noExposeCtrl.mountPoint!.path, 'no_expose'); }); test('mounts correct routes', () { @@ -182,7 +182,7 @@ main() { test('mounts correct methods', () { void expectMethod(String name, String method) { - expect(noExposeCtrl.routeMappings[name].method, method); + expect(noExposeCtrl.routeMappings[name]!.method, method); } expectMethod('getIndex', 'GET'); @@ -194,7 +194,7 @@ main() { test('mounts correct paths', () { void expectPath(String name, String path) { - expect(noExposeCtrl.routeMappings[name].path, path); + expect(noExposeCtrl.routeMappings[name]!.path, path); } expectPath('getIndex', '/'); diff --git a/packages/framework/test/detach_test.dart b/packages/framework/test/detach_test.dart index fcd4af84..cddde978 100644 --- a/packages/framework/test/detach_test.dart +++ b/packages/framework/test/detach_test.dart @@ -5,7 +5,7 @@ import 'package:mock_request/mock_request.dart'; import 'package:test/test.dart'; void main() { - AngelHttp http; + late AngelHttp http; setUp(() async { var app = Angel(); diff --git a/packages/framework/test/di_test.dart b/packages/framework/test/di_test.dart index 26e902b8..51e9999e 100644 --- a/packages/framework/test/di_test.dart +++ b/packages/framework/test/di_test.dart @@ -15,31 +15,31 @@ final String TEXT = "make your bed"; final String OVER = "never"; main() { - Angel app; - http.Client client; - HttpServer server; - String url; + Angel? app; + http.Client? client; + late HttpServer server; + String? url; setUp(() async { app = Angel(reflector: MirrorsReflector()); client = http.Client(); // Inject some todos - app.container.registerSingleton(Todo(text: TEXT, over: OVER)); - app.container.registerFactory>((container) async { - var req = container.make(); - var text = await utf8.decoder.bind(req.body).join(); + app!.container!.registerSingleton(Todo(text: TEXT, over: OVER)); + app!.container!.registerFactory>((container) async { + var req = container.make()!; + var text = await utf8.decoder.bind(req.body!).join(); return Foo(text); }); - app.get("/errands", ioc((Todo singleton) => singleton)); - app.get( + app!.get("/errands", ioc((Todo singleton) => singleton)); + app!.get( "/errands3", - ioc(({Errand singleton, Todo foo, RequestContext req}) => + ioc(({required Errand singleton, Todo? foo, RequestContext? req}) => singleton.text)); - app.post('/async', ioc((Foo foo) => {'baz': foo.bar})); - await app.configure(SingletonController().configureServer); - await app.configure(ErrandController().configureServer); + app!.post('/async', ioc((Foo foo) => {'baz': foo.bar})); + await app!.configure(SingletonController().configureServer); + await app!.configure(ErrandController().configureServer); server = await AngelHttp(app).startServer(); url = "http://${server.address.host}:${server.port}"; @@ -48,7 +48,7 @@ main() { tearDown(() async { app = null; url = null; - client.close(); + client!.close(); client = null; await server.close(force: true); }); @@ -71,33 +71,33 @@ main() { }); test("singleton in route", () async { - validateTodoSingleton(await client.get(Uri.parse("$url/errands"))); + validateTodoSingleton(await client!.get(Uri.parse("$url/errands"))); }); test("singleton in controller", () async { - validateTodoSingleton(await client.get(Uri.parse("$url/errands2"))); + validateTodoSingleton(await client!.get(Uri.parse("$url/errands2"))); }); test("make in route", () async { - var response = await client.get(Uri.parse("$url/errands3")); - var text = await json.decode(response.body) as String; + var response = await client!.get(Uri.parse("$url/errands3")); + var text = await json.decode(response.body) as String?; expect(text, equals(TEXT)); }); test("make in controller", () async { - var response = await client.get(Uri.parse("$url/errands4")); - var text = await json.decode(response.body) as String; + var response = await client!.get(Uri.parse("$url/errands4")); + var text = await json.decode(response.body) as String?; expect(text, equals(TEXT)); }); test('resolve from future in controller', () async { var response = - await client.post(Uri.parse('$url/errands4/async'), body: 'hey'); + await client!.post(Uri.parse('$url/errands4/async'), body: 'hey'); expect(response.body, json.encode({'bar': 'hey'})); }); test('resolve from future in route', () async { - var response = await client.post(Uri.parse('$url/async'), body: 'yes'); + var response = await client!.post(Uri.parse('$url/async'), body: 'yes'); expect(response.body, json.encode({'baz': 'yes'})); }); } @@ -137,7 +137,7 @@ class Foo { class Errand { Todo todo; - String get text => todo.text; + String? get text => todo.text; Errand(this.todo); } diff --git a/packages/framework/test/encoders_buffer_test.dart b/packages/framework/test/encoders_buffer_test.dart index 1b41b9c3..8aa9c7cd 100644 --- a/packages/framework/test/encoders_buffer_test.dart +++ b/packages/framework/test/encoders_buffer_test.dart @@ -17,7 +17,7 @@ Future> getBody(MockHttpResponse rs) async { } main() { - Angel app; + late Angel app; setUp(() { app = Angel(reflector: MirrorsReflector()); @@ -43,7 +43,7 @@ main() { void encodingTests(Angel getApp()) { group('encoding', () { Angel app; - AngelHttp http; + late AngelHttp http; setUp(() { app = getApp(); diff --git a/packages/framework/test/general_test.dart b/packages/framework/test/general_test.dart index f0021711..b279c6c3 100644 --- a/packages/framework/test/general_test.dart +++ b/packages/framework/test/general_test.dart @@ -7,10 +7,10 @@ import 'package:http/http.dart' as http; import 'package:test/test.dart'; main() { - Angel app; - http.Client client; - HttpServer server; - String url; + Angel? app; + http.Client? client; + late HttpServer server; + String? url; setUp(() async { app = Angel(reflector: MirrorsReflector()) @@ -25,13 +25,13 @@ main() { tearDown(() async { app = null; url = null; - client.close(); + client!.close(); client = null; await server.close(force: true); }); test("allow override of method", () async { - var response = await client.get(Uri.parse('$url/foo'), + var response = await client!.get(Uri.parse('$url/foo'), headers: {'X-HTTP-Method-Override': 'POST'}); print('Response: ${response.body}'); expect(json.decode(response.body), equals({'hello': 'world'})); diff --git a/packages/framework/test/hm.dart b/packages/framework/test/hm.dart index fb10cec0..a7c08db5 100644 --- a/packages/framework/test/hm.dart +++ b/packages/framework/test/hm.dart @@ -6,7 +6,7 @@ main() async { var zone = Zone.current.fork( specification: ZoneSpecification(print: (self, parent, zone, line) { if (line == 'null') { - parent.print(zone, cyan.wrap(StackTrace.current.toString())); + parent.print(zone, cyan.wrap(StackTrace.current.toString())!); } }), ); diff --git a/packages/framework/test/hooked_test.dart b/packages/framework/test/hooked_test.dart index 0382e30a..7b01ac05 100644 --- a/packages/framework/test/hooked_test.dart +++ b/packages/framework/test/hooked_test.dart @@ -13,26 +13,26 @@ main() { 'Content-Type': 'application/json' }; - Angel app; - HttpServer server; - String url; - http.Client client; - HookedService todoService; + Angel? app; + late HttpServer server; + String? url; + http.Client? client; + HookedService? todoService; setUp(() async { app = Angel(reflector: MirrorsReflector()); client = http.Client(); - app.use('/todos', MapService()); - app.use('/books', BookService()); + app!.use('/todos', MapService()); + app!.use('/books', BookService()); - todoService = app.findHookedService('todos'); + todoService = app!.findHookedService('todos'); - todoService.beforeAllStream().listen((e) { + todoService!.beforeAllStream().listen((e) { print('Fired ${e.eventName}! Data: ${e.data}; Params: ${e.params}'); }); - app.errorHandler = (e, req, res) { - throw e.error; + app!.errorHandler = (e, req, res) { + throw e.error as Object; }; server = await AngelHttp(app).startServer(); @@ -43,7 +43,7 @@ main() { await server.close(force: true); app = null; url = null; - client.close(); + client!.close(); client = null; todoService = null; }); @@ -52,20 +52,20 @@ main() { int count = 0; todoService - ..beforeIndexed.listen((_) { + ?..beforeIndexed.listen((_) { count++; }) ..afterIndexed.listen((_) { count++; }); - var response = await client.get(Uri.parse("$url/todos")); + var response = await client!.get(Uri.parse("$url/todos")); print(response.body); expect(count, equals(2)); }); test("cancel before", () async { - todoService.beforeCreated + todoService!.beforeCreated ..listen((HookedServiceEvent event) { event.cancel({"hello": "hooked world"}); }) @@ -73,7 +73,7 @@ main() { event.cancel({"this_hook": "should never run"}); }); - var response = await client.post(Uri.parse("$url/todos"), + var response = await client!.post(Uri.parse("$url/todos"), body: json.encode({"arbitrary": "data"}), headers: headers as Map); print(response.body); @@ -82,7 +82,7 @@ main() { }); test("cancel after", () async { - todoService.afterIndexed + todoService!.afterIndexed ..listen((HookedServiceEvent event) async { // Hooks can be Futures ;) event.cancel([ @@ -93,20 +93,20 @@ main() { event.cancel({"this_hook": "should never run either"}); }); - var response = await client.get(Uri.parse("$url/todos")); + var response = await client!.get(Uri.parse("$url/todos")); print(response.body); var result = json.decode(response.body) as List; expect(result[0]["angel"], equals("framework")); }); test('asStream() fires', () async { - var stream = todoService.afterCreated.asStream(); - await todoService.create({'angel': 'framework'}); + var stream = todoService!.afterCreated.asStream(); + await todoService!.create({'angel': 'framework'}); expect(await stream.first.then((e) => e.result['angel']), 'framework'); }); test('metadata', () async { - final service = HookedService(IncrementService())..addHooks(app); + final service = HookedService(IncrementService())..addHooks(app!); expect(service.inner, isNot(const IsInstanceOf())); IncrementService.TIMES = 0; await service.index(); @@ -114,14 +114,15 @@ main() { }); test('inject request + response', () async { - HookedService books = app.findService('books'); + HookedService books = app!.findService('books') + as HookedService>; books.beforeIndexed.listen((e) { expect([e.request, e.response], everyElement(isNotNull)); - print('Indexing books at path: ${e.request.path}'); + print('Indexing books at path: ${e.request!.path}'); }); - var response = await client.get(Uri.parse('$url/books')); + var response = await client!.get(Uri.parse('$url/books')); print(response.body); var result = json.decode(response.body); @@ -137,8 +138,8 @@ main() { var type = e.isBefore ? 'before' : 'after'; print('Params to $type ${e.eventName}: ${e.params}'); expect(e.params, isMap); - expect(e.params.keys, contains('provider')); - expect(e.params['provider'], const IsInstanceOf()); + expect(e.params!.keys, contains('provider')); + expect(e.params!['provider'], const IsInstanceOf()); } svc diff --git a/packages/framework/test/http2/adapter_test.dart b/packages/framework/test/http2/adapter_test.dart index 7773dc17..3ffcb541 100644 --- a/packages/framework/test/http2/adapter_test.dart +++ b/packages/framework/test/http2/adapter_test.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:angel_container/mirrors.dart'; import 'package:angel_framework/angel_framework.dart' hide Header; import 'package:angel_framework/http2.dart'; +import 'package:collection/collection.dart' show IterableExtension; import 'package:http/src/multipart_file.dart' as http; import 'package:http/src/multipart_request.dart' as http; import 'package:http/io_client.dart'; @@ -22,10 +23,10 @@ Stream> jfkStream() { void main() { var client = Http2Client(); - IOClient h1c; + late IOClient h1c; Angel app; - AngelHttp2 http2; - Uri serverRoot; + late AngelHttp2 http2; + late Uri serverRoot; setUp(() async { app = Angel(reflector: MirrorsReflector())..encoders['gzip'] = gzip.encoder; @@ -63,11 +64,13 @@ void main() { app.post('/upload', (req, res) async { await req.parseBody(); - var body = req.bodyAsMap, files = req.uploadedFiles; - var file = files.firstWhere((f) => f.name == 'file'); + var body = req.bodyAsMap; + List files = req.uploadedFiles ?? []; + + UploadedFile file = files.firstWhereOrNull((f) => f.name == 'file')!; return [ await file.data.map((l) => l.length).reduce((a, b) => a + b), - file.contentType.mimeType, + file.contentType!.mimeType, body ]; }); @@ -103,13 +106,13 @@ void main() { http2 = AngelHttp2(app, ctx, allowHttp1: true); - var server = await http2.startServer(); + SecureServerSocket server = await http2.startServer(); serverRoot = Uri.parse('https://127.0.0.1:${server.port}'); }); tearDown(() async { await http2.close(); - await h1c.close(); + h1c.close(); }); test('buffered response', () async { @@ -167,7 +170,7 @@ void main() { test('json response', () async { var response = await client.get(serverRoot.replace(path: '/json')); expect(response.body, json.encode({'foo': 'bar'})); - expect(ContentType.parse(response.headers['content-type']).mimeType, + expect(ContentType.parse(response.headers['content-type']!).mimeType, ContentType.json.mimeType); }); diff --git a/packages/framework/test/http2/http2_client.dart b/packages/framework/test/http2/http2_client.dart index efdea1f7..a4520ba8 100644 --- a/packages/framework/test/http2/http2_client.dart +++ b/packages/framework/test/http2/http2_client.dart @@ -89,11 +89,11 @@ class Http2Client extends BaseClient { var closed = await readResponse(stream, headers, body); return StreamedResponse( Stream.fromIterable([body.takeBytes()]), - int.parse(headers[':status']), + int.parse(headers[':status']!), headers: headers, isRedirect: headers.containsKey('location'), contentLength: headers.containsKey('content-length') - ? int.parse(headers['content-length']) + ? int.parse(headers['content-length']!) : null, request: request, reasonPhrase: null, diff --git a/packages/framework/test/parameter_meta_test.dart b/packages/framework/test/parameter_meta_test.dart index cb46f9d3..162acb70 100644 --- a/packages/framework/test/parameter_meta_test.dart +++ b/packages/framework/test/parameter_meta_test.dart @@ -24,7 +24,7 @@ void main() { parameterMetaTests() { Angel app; - AngelHttp http; + late AngelHttp http; setUp(() { app = Angel(reflector: MirrorsReflector()); diff --git a/packages/framework/test/primitives_test.dart b/packages/framework/test/primitives_test.dart index 63126450..0e14bdc8 100644 --- a/packages/framework/test/primitives_test.dart +++ b/packages/framework/test/primitives_test.dart @@ -9,8 +9,8 @@ import 'package:mock_request/mock_request.dart'; import 'package:test/test.dart'; main() { - Angel app; - AngelHttp http; + late Angel app; + late AngelHttp http; setUp(() { app = Angel(reflector: MirrorsReflector()) diff --git a/packages/framework/test/req_shutdown_test.dart b/packages/framework/test/req_shutdown_test.dart index 01b1e551..9ea178ec 100644 --- a/packages/framework/test/req_shutdown_test.dart +++ b/packages/framework/test/req_shutdown_test.dart @@ -7,10 +7,10 @@ import 'package:test/test.dart'; import 'pretty_log.dart'; void main() { - http.IOClient client; - AngelHttp driver; - Logger logger; - StringBuffer buf; + late http.IOClient client; + late AngelHttp driver; + late Logger logger; + late StringBuffer buf; setUp(() async { buf = StringBuffer(); diff --git a/packages/framework/test/routing_test.dart b/packages/framework/test/routing_test.dart index d0d6477b..97469472 100644 --- a/packages/framework/test/routing_test.dart +++ b/packages/framework/test/routing_test.dart @@ -20,7 +20,7 @@ testMiddlewareMetadata(RequestContext req, ResponseContext res) async { class QueryService extends Service { @override @Middleware([interceptor]) - read(id, [Map params]) async => params; + read(id, [Map? params]) async => params; } void interceptor(RequestContext req, ResponseContext res) { @@ -35,19 +35,19 @@ bool interceptService(RequestContext req, ResponseContext res) { } main() { - Angel app; - Angel nested; - Angel todos; - String url; - http.Client client; + Angel? app; + Angel? nested; + Angel? todos; + String? url; + http.Client? client; setUp(() async { app = Angel(reflector: MirrorsReflector()); nested = Angel(reflector: MirrorsReflector()); todos = Angel(reflector: MirrorsReflector()); - [app, nested, todos].forEach((Angel app) { - app.logger = Logger('routing_test') + [app, nested, todos].forEach((Angel? app) { + app!.logger = Logger('routing_test') ..onRecord.listen((rec) { if (rec.error != null) { stdout @@ -58,44 +58,44 @@ main() { }); }); - todos.get('/action/:action', (req, res) => res.json(req.params)); + todos!.get('/action/:action', (req, res) => res.json(req.params)); - Route ted; + late Route ted; - ted = nested.post('/ted/:route', (RequestContext req, res) { + ted = nested!.post('/ted/:route', (RequestContext req, res) { print('Params: ${req.params}'); print('Path: ${ted.path}, uri: ${req.path}'); print('matcher: ${ted.parser}'); return req.params; }); - app.mount('/nes', nested); - app.get('/meta', testMiddlewareMetadata); - app.get('/intercepted', (req, res) => 'This should not be shown', + app!.mount('/nes', nested!); + app!.get('/meta', testMiddlewareMetadata); + app!.get('/intercepted', (req, res) => 'This should not be shown', middleware: [interceptor]); - app.get('/hello', (req, res) => 'world'); - app.get('/name/:first/last/:last', (req, res) => req.params); - app.post( + app!.get('/hello', (req, res) => 'world'); + app!.get('/name/:first/last/:last', (req, res) => req.params); + app!.post( '/lambda', (RequestContext req, res) => req.parseBody().then((_) => req.bodyAsMap)); - app.mount('/todos/:id', todos); - app + app!.mount('/todos/:id', todos!); + app! .get('/greet/:name', (RequestContext req, res) async => "Hello ${req.params['name']}") .name = 'Named routes'; - app.get('/named', (req, ResponseContext res) async { + app!.get('/named', (req, ResponseContext res) async { await res.redirectTo('Named routes', {'name': 'tests'}); }); - app.get('/log', (RequestContext req, res) async { + app!.get('/log', (RequestContext req, res) async { print("Query: ${req.queryParameters}"); return "Logged"; }); - app.get('/method', (req, res) => 'Only GET'); - app.post('/method', (req, res) => 'Only POST'); + app!.get('/method', (req, res) => 'Only GET'); + app!.post('/method', (req, res) => 'Only POST'); - app.use('/query', QueryService()); + app!.use('/query', QueryService()); RequestHandler write(String message) { return (req, res) { @@ -104,35 +104,35 @@ main() { }; } - app.chain([write('a')]).chain([write('b'), write('c')]).get( + app!.chain([write('a')]).chain([write('b'), write('c')]).get( '/chained', (req, res) => res.close()); - app.fallback((req, res) => 'MJ'); + app!.fallback((req, res) => 'MJ'); //app.dumpTree(header: "DUMPING ROUTES:", showMatchers: true); client = http.Client(); - var server = await AngelHttp(app).startServer('127.0.0.1', 0); + HttpServer server = await AngelHttp(app).startServer('127.0.0.1', 0); url = "http://${server.address.host}:${server.port}"; }); tearDown(() async { - await app.close(); + await app!.close(); app = null; nested = null; todos = null; - client.close(); + client!.close(); client = null; url = null; }); test('Can match basic url', () async { - var response = await client.get(Uri.parse("$url/hello")); + var response = await client!.get(Uri.parse("$url/hello")); expect(response.body, equals('"world"')); }); test('Can match url with multiple parameters', () async { - var response = await client.get(Uri.parse('$url/name/HELLO/last/WORLD')); + var response = await client!.get(Uri.parse('$url/name/HELLO/last/WORLD')); print('Response: ${response.body}'); var json_ = json.decode(response.body); expect(json_, const IsInstanceOf()); @@ -141,18 +141,18 @@ main() { }); test('Chained routes', () async { - var response = await client.get(Uri.parse("$url/chained")); + var response = await client!.get(Uri.parse("$url/chained")); expect(response.body, equals('abc')); }); test('Can nest another Angel instance', () async { - var response = await client.post(Uri.parse('$url/nes/ted/foo')); + var response = await client!.post(Uri.parse('$url/nes/ted/foo')); var json_ = json.decode(response.body); expect(json_['route'], equals('foo')); }); test('Can parse parameters from a nested Angel instance', () async { - var response = await client.get(Uri.parse('$url/todos/1337/action/test')); + var response = await client!.get(Uri.parse('$url/todos/1337/action/test')); var json_ = json.decode(response.body); print('JSON: $json_'); expect(json_['id'], equals('1337')); @@ -160,32 +160,32 @@ main() { }); test('Can add and use named middleware', () async { - var response = await client.get(Uri.parse('$url/intercepted')); + var response = await client!.get(Uri.parse('$url/intercepted')); expect(response.body, equals('Middleware')); }); test('Middleware via metadata', () async { // Metadata - var response = await client.get(Uri.parse('$url/meta')); + var response = await client!.get(Uri.parse('$url/meta')); expect(response.body, equals('Middleware')); }); test('Can serialize function result as JSON', () async { Map headers = {'Content-Type': 'application/json'}; String postData = json.encode({'it': 'works'}); - var response = await client.post(Uri.parse("$url/lambda"), + var response = await client!.post(Uri.parse("$url/lambda"), headers: headers as Map, body: postData); print('Response: ${response.body}'); expect(json.decode(response.body)['it'], equals('works')); }); test('Fallback routes', () async { - var response = await client.get(Uri.parse('$url/my_favorite_artist')); + var response = await client!.get(Uri.parse('$url/my_favorite_artist')); expect(response.body, equals('"MJ"')); }); test('Can name routes', () { - Route foo = app.get('/framework/:id', null)..name = 'frm'; + Route foo = app!.get('/framework/:id', null)..name = 'frm'; print('Foo: $foo'); String uri = foo.makeUri({'id': 'angel'}); print(uri); @@ -193,32 +193,32 @@ main() { }); test('Redirect to named routes', () async { - var response = await client.get(Uri.parse('$url/named')); + var response = await client!.get(Uri.parse('$url/named')); print(response.body); expect(json.decode(response.body), equals('Hello tests')); }); test('Match routes, even with query params', () async { - var response = await client + var response = await client! .get(Uri.parse("$url/log?foo=bar&bar=baz&baz.foo=bar&baz.bar=foo")); print(response.body); expect(json.decode(response.body), equals('Logged')); - response = await client.get(Uri.parse("$url/query/foo?bar=baz")); + response = await client!.get(Uri.parse("$url/query/foo?bar=baz")); print(response.body); expect(response.body, equals("Service with Middleware")); }); test('only match route with matching method', () async { - var response = await client.get(Uri.parse("$url/method")); + var response = await client!.get(Uri.parse("$url/method")); print(response.body); expect(response.body, '"Only GET"'); - response = await client.post(Uri.parse("$url/method")); + response = await client!.post(Uri.parse("$url/method")); print(response.body); expect(response.body, '"Only POST"'); - response = await client.patch(Uri.parse("$url/method")); + response = await client!.patch(Uri.parse("$url/method")); print(response.body); expect(response.body, '"MJ"'); }); diff --git a/packages/framework/test/serialize_test.dart b/packages/framework/test/serialize_test.dart index 723be706..d9d20ce4 100644 --- a/packages/framework/test/serialize_test.dart +++ b/packages/framework/test/serialize_test.dart @@ -8,10 +8,10 @@ import 'package:http_parser/http_parser.dart'; import 'package:test/test.dart'; main() { - Angel app; - http.Client client; - HttpServer server; - String url; + Angel? app; + http.Client? client; + late HttpServer server; + String? url; setUp(() async { app = Angel(reflector: MirrorsReflector()) @@ -29,17 +29,17 @@ main() { tearDown(() async { app = null; url = null; - client.close(); + client!.close(); client = null; await server.close(force: true); }); test("correct content-type", () async { - var response = await client.get(Uri.parse('$url/foo')); + var response = await client!.get(Uri.parse('$url/foo')); print('Response: ${response.body}'); expect(response.headers['content-type'], contains('application/json')); - response = await client.get(Uri.parse('$url/bar')); + response = await client!.get(Uri.parse('$url/bar')); print('Response: ${response.body}'); expect(response.headers['content-type'], contains('text/html')); }); diff --git a/packages/framework/test/server_test.dart b/packages/framework/test/server_test.dart index 88b15343..2f4e5c5e 100644 --- a/packages/framework/test/server_test.dart +++ b/packages/framework/test/server_test.dart @@ -51,7 +51,7 @@ main() { await app.errorHandler(e, req, res); await http.sendResponse(rq, rs, req, res); expect( - ContentType.parse(rs.headers.value('content-type')).mimeType, + ContentType.parse(rs.headers.value('content-type')!).mimeType, 'text/html', ); expect(rs.statusCode, e.statusCode); @@ -116,8 +116,8 @@ main() { var http = AngelHttp(app); app.responseFinalizers .add((req, res) => throw AngelHttpException.forbidden()); - RequestContext req; - ResponseContext res; + late RequestContext req; + late ResponseContext res; setUp(() async { var rq = MockHttpRequest('GET', $foo); @@ -155,8 +155,8 @@ main() { }); group('handleAngelHttpException', () { - Angel app; - AngelHttp http; + late Angel app; + late AngelHttp http; setUp(() async { app = Angel(reflector: MirrorsReflector()); @@ -182,7 +182,7 @@ main() { (http.handleRequest(rq)); await rq.response.toList(); expect(rq.response.statusCode, 403); - expect(rq.response.headers.contentType.mimeType, 'application/json'); + expect(rq.response.headers.contentType!.mimeType, 'application/json'); }); test('can throw in finalizer', () async { @@ -192,7 +192,7 @@ main() { (http.handleRequest(rq)); await rq.response.toList(); expect(rq.response.statusCode, 403); - expect(rq.response.headers.contentType.mimeType, 'application/json'); + expect(rq.response.headers.contentType!.mimeType, 'application/json'); }); test('can send html', () async { diff --git a/packages/framework/test/service_map_test.dart b/packages/framework/test/service_map_test.dart index 99346829..4ca9d1c5 100644 --- a/packages/framework/test/service_map_test.dart +++ b/packages/framework/test/service_map_test.dart @@ -3,7 +3,7 @@ import 'package:test/test.dart'; void main() { MapService inner; - Service mapped; + late Service mapped; setUp(() { inner = MapService(); @@ -22,8 +22,8 @@ void main() { }); group('after create', () { - Todo result; - String id; + late Todo result; + String? id; setUp(() async { result = await mapped.create(Todo(text: 'hello', complete: false)); @@ -55,16 +55,16 @@ void main() { } class Todo { - final String id, text; - final bool complete; + final String? id, text; + final bool? complete; Todo({this.id, this.text, this.complete}); static Todo fromMap(Map json) { return Todo( - id: json['id'] as String, - text: json['text'] as String, - complete: json['complete'] as bool); + id: json['id'] as String?, + text: json['text'] as String?, + complete: json['complete'] as bool?); } static Map toMap(Todo model) { diff --git a/packages/framework/test/services_test.dart b/packages/framework/test/services_test.dart index 0677883c..f5a0717e 100644 --- a/packages/framework/test/services_test.dart +++ b/packages/framework/test/services_test.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:angel_container/mirrors.dart'; import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/http.dart'; @@ -7,8 +9,8 @@ import 'package:stack_trace/stack_trace.dart'; import 'package:test/test.dart'; class Todo extends Model { - String text; - String over; + String? text; + String? over; } main() { @@ -16,35 +18,35 @@ main() { 'Accept': 'application/json', 'Content-Type': 'application/json' }; - Angel app; - MapService service; - String url; - http.Client client; + Angel? app; + late MapService service; + String? url; + http.Client? client; setUp(() async { app = Angel(reflector: MirrorsReflector()) ..use('/todos', service = MapService()) ..errorHandler = (e, req, res) { if (e.error != null) print('Whoops: ${e.error}'); - if (e.stackTrace != null) print(Chain.forTrace(e.stackTrace).terse); + if (e.stackTrace != null) print(Chain.forTrace(e.stackTrace!).terse); }; - var server = await AngelHttp(app).startServer(); + HttpServer server = await AngelHttp(app).startServer(); client = http.Client(); url = "http://${server.address.host}:${server.port}"; }); tearDown(() async { - await app.close(); + await app!.close(); app = null; url = null; - client.close(); + client!.close(); client = null; }); group('memory', () { test('can index an empty service', () async { - var response = await client.get(Uri.parse("$url/todos/")); + var response = await client!.get(Uri.parse("$url/todos/")); print(response.body); expect(response.body, equals('[]')); print(response.body); @@ -53,7 +55,7 @@ main() { test('can create data', () async { String postData = json.encode({'text': 'Hello, world!'}); - var response = await client.post(Uri.parse("$url/todos"), + var response = await client!.post(Uri.parse("$url/todos"), headers: headers as Map, body: postData); expect(response.statusCode, 201); var jsons = json.decode(response.body); @@ -63,9 +65,9 @@ main() { test('can fetch data', () async { String postData = json.encode({'text': 'Hello, world!'}); - await client.post(Uri.parse("$url/todos"), + await client!.post(Uri.parse("$url/todos"), headers: headers as Map, body: postData); - var response = await client.get(Uri.parse("$url/todos/0")); + var response = await client!.get(Uri.parse("$url/todos/0")); expect(response.statusCode, 200); var jsons = json.decode(response.body); print(jsons); @@ -74,12 +76,12 @@ main() { test('can modify data', () async { String postData = json.encode({'text': 'Hello, world!'}); - await client.post(Uri.parse("$url/todos"), + await client!.post(Uri.parse("$url/todos"), headers: headers as Map, body: postData); postData = json.encode({'text': 'modified'}); - var response = await client.patch(Uri.parse("$url/todos/0"), - headers: headers as Map, body: postData); + var response = await client! + .patch(Uri.parse("$url/todos/0"), headers: headers, body: postData); expect(response.statusCode, 200); var jsons = json.decode(response.body); print(jsons); @@ -88,12 +90,12 @@ main() { test('can overwrite data', () async { String postData = json.encode({'text': 'Hello, world!'}); - await client.post(Uri.parse("$url/todos"), + await client!.post(Uri.parse("$url/todos"), headers: headers as Map, body: postData); postData = json.encode({'over': 'write'}); - var response = await client.post(Uri.parse("$url/todos/0"), - headers: headers as Map, body: postData); + var response = await client! + .post(Uri.parse("$url/todos/0"), headers: headers, body: postData); expect(response.statusCode, 200); var jsons = json.decode(response.body); print(jsons); @@ -108,18 +110,18 @@ main() { await service.create({'baz': 'quux'}) ]; - var ids = items.map((m) => m['id'] as String).toList(); + var ids = items.map((m) => m['id'] as String?).toList(); expect(await service.readMany(ids), items); }); test('can delete data', () async { String postData = json.encode({'text': 'Hello, world!'}); - var created = await client + var created = await client! .post(Uri.parse("$url/todos"), headers: headers as Map, body: postData) .then((r) => json.decode(r.body)); var response = - await client.delete(Uri.parse("$url/todos/${created['id']}")); + await client!.delete(Uri.parse("$url/todos/${created['id']}")); expect(response.statusCode, 200); var json_ = json.decode(response.body); print(json_); @@ -127,7 +129,7 @@ main() { }); test('cannot remove all unless explicitly set', () async { - var response = await client.delete(Uri.parse('$url/todos/null')); + var response = await client!.delete(Uri.parse('$url/todos/null')); expect(response.statusCode, 403); }); }); diff --git a/packages/framework/test/streaming_test.dart b/packages/framework/test/streaming_test.dart index bb0a0777..0face158 100644 --- a/packages/framework/test/streaming_test.dart +++ b/packages/framework/test/streaming_test.dart @@ -14,8 +14,8 @@ import 'package:test/test.dart'; import 'encoders_buffer_test.dart' show encodingTests; main() { - Angel app; - AngelHttp http; + late Angel app; + late AngelHttp http; setUp(() { app = Angel(reflector: MirrorsReflector()); diff --git a/packages/framework/test/view_generator_test.dart b/packages/framework/test/view_generator_test.dart index 1df69acf..c25d2755 100644 --- a/packages/framework/test/view_generator_test.dart +++ b/packages/framework/test/view_generator_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; main() { test('default view generator', () async { var app = Angel(); - var view = await app.viewGenerator('foo', {'bar': 'baz'}); + var view = await app.viewGenerator!('foo', {'bar': 'baz'}); expect(view, contains('No view engine')); }); } From 5302f635acd83caad128aab80f9549a32879012e Mon Sep 17 00:00:00 2001 From: thomashii Date: Sun, 21 Mar 2021 07:51:20 +0800 Subject: [PATCH 014/171] Migrated angel_auth to NNBD --- CHANGELOG.md | 2 +- packages/auth/example/example.dart | 8 +- packages/auth/lib/src/auth_token.dart | 61 +++--- packages/auth/lib/src/configuration.dart | 29 ++- .../auth/lib/src/middleware/require_auth.dart | 20 +- packages/auth/lib/src/options.dart | 14 +- packages/auth/lib/src/plugin.dart | 193 +++++++++--------- packages/auth/lib/src/strategies/local.dart | 60 +++--- packages/auth/lib/src/strategy.dart | 2 +- packages/auth/pubspec.yaml | 6 +- packages/auth/test/callback_test.dart | 68 +++--- packages/auth/test/local_test.dart | 26 +-- packages/auth/test/protect_cookie_test.dart | 4 +- 13 files changed, 257 insertions(+), 236 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a220dd91..c8f598be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ * Added merge_map and updated to 2.0.0 * Added mock_request and updated to 2.0.0 * Updated angel_framework to 4.0.0 (Revisit TODO) -* Updated angel_auth to 4.0.0 (todo) +* Updated angel_auth to 4.0.0 * Updated angel_configuration to 4.0.0 (todo) # 3.0.0 (Non NNBD) diff --git a/packages/auth/example/example.dart b/packages/auth/example/example.dart index 90e09f2b..87894ddb 100644 --- a/packages/auth/example/example.dart +++ b/packages/auth/example/example.dart @@ -3,11 +3,11 @@ import 'package:angel_auth/angel_auth.dart'; import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/http.dart'; -main() async { +void main() async { var app = Angel(); - var auth = AngelAuth(); + var auth = AngelAuth(); - auth.serializer = (user) => user.id; + auth.serializer = (user) => user!.id; auth.deserializer = (id) => fetchAUserByIdSomehow(id); @@ -30,7 +30,7 @@ main() async { } class User { - String id, username, password; + String? id, username, password; } Future fetchAUserByIdSomehow(id) async { diff --git a/packages/auth/lib/src/auth_token.dart b/packages/auth/lib/src/auth_token.dart index 338303ef..053b22b5 100644 --- a/packages/auth/lib/src/auth_token.dart +++ b/packages/auth/lib/src/auth_token.dart @@ -26,11 +26,11 @@ String decodeBase64(String str) { class AuthToken { final SplayTreeMap _header = - SplayTreeMap.from({"alg": "HS256", "typ": "JWT"}); + SplayTreeMap.from({'alg': 'HS256', 'typ': 'JWT'}); - String ipAddress; - DateTime issuedAt; - num lifeSpan; + String? ipAddress; + late DateTime issuedAt; + num? lifeSpan; var userId; Map payload = {}; @@ -38,12 +38,15 @@ class AuthToken { {this.ipAddress, this.lifeSpan = -1, this.userId, - DateTime issuedAt, + DateTime? issuedAt, Map payload = const {}}) { this.issuedAt = issuedAt ?? DateTime.now(); - this.payload.addAll( - payload?.keys?.fold({}, (out, k) => out..[k.toString()] = payload[k]) ?? - {}); + this.payload.addAll(payload?.keys?.fold( + {}, + ((out, k) => out..[k.toString()] = payload[k]) + as Map? Function( + Map?, dynamic)) ?? + {}); } factory AuthToken.fromJson(String jsons) => @@ -51,37 +54,40 @@ class AuthToken { factory AuthToken.fromMap(Map data) { return AuthToken( - ipAddress: data["aud"].toString(), - lifeSpan: data["exp"] as num, - issuedAt: DateTime.parse(data["iat"].toString()), - userId: data["sub"], - payload: data["pld"] as Map ?? {}); + ipAddress: data['aud'].toString(), + lifeSpan: data['exp'] as num?, + issuedAt: DateTime.parse(data['iat'].toString()), + userId: data['sub'], + payload: data['pld'] as Map? ?? {}); } factory AuthToken.parse(String jwt) { - var split = jwt.split("."); + var split = jwt.split('.'); - if (split.length != 3) - throw AngelHttpException.notAuthenticated(message: "Invalid JWT."); + if (split.length != 3) { + throw AngelHttpException.notAuthenticated(message: 'Invalid JWT.'); + } var payloadString = decodeBase64(split[1]); return AuthToken.fromMap(json.decode(payloadString) as Map); } factory AuthToken.validate(String jwt, Hmac hmac) { - var split = jwt.split("."); + var split = jwt.split('.'); - if (split.length != 3) - throw AngelHttpException.notAuthenticated(message: "Invalid JWT."); + if (split.length != 3) { + throw AngelHttpException.notAuthenticated(message: 'Invalid JWT.'); + } // var headerString = decodeBase64(split[0]); var payloadString = decodeBase64(split[1]); - var data = split[0] + "." + split[1]; + var data = split[0] + '.' + split[1]; var signature = base64Url.encode(hmac.convert(data.codeUnits).bytes); - if (signature != split[2]) + if (signature != split[2]) { throw AngelHttpException.notAuthenticated( - message: "JWT payload does not match hashed version."); + message: 'JWT payload does not match hashed version.'); + } return AuthToken.fromMap(json.decode(payloadString) as Map); } @@ -89,9 +95,9 @@ class AuthToken { String serialize(Hmac hmac) { var headerString = base64Url.encode(json.encode(_header).codeUnits); var payloadString = base64Url.encode(json.encode(toJson()).codeUnits); - var data = headerString + "." + payloadString; + var data = headerString + '.' + payloadString; var signature = hmac.convert(data.codeUnits).bytes; - return data + "." + base64Url.encode(signature); + return data + '.' + base64Url.encode(signature); } Map toJson() { @@ -114,11 +120,12 @@ SplayTreeMap _splayify(Map map) { return SplayTreeMap.from(data); } -_splay(value) { +dynamic _splay(value) { if (value is Iterable) { return value.map(_splay).toList(); - } else if (value is Map) + } else if (value is Map) { return _splayify(value); - else + } else { return value; + } } diff --git a/packages/auth/lib/src/configuration.dart b/packages/auth/lib/src/configuration.dart index 65cebb94..d2529440 100644 --- a/packages/auth/lib/src/configuration.dart +++ b/packages/auth/lib/src/configuration.dart @@ -1,15 +1,14 @@ import 'package:charcode/ascii.dart'; import 'package:collection/collection.dart'; -import 'package:meta/meta.dart'; -import 'package:quiver_hashcode/hashcode.dart'; +import 'package:quiver/core.dart'; /// A common class containing parsing and validation logic for third-party authentication configuration. class ExternalAuthOptions { /// The user's identifier, otherwise known as an "application id". - final String clientId; + final String? clientId; /// The user's secret, other known as an "application secret". - final String clientSecret; + final String? clientSecret; /// The user's redirect URI. final Uri redirectUri; @@ -27,9 +26,9 @@ class ExternalAuthOptions { } factory ExternalAuthOptions( - {@required String clientId, - @required String clientSecret, - @required redirectUri, + {required String? clientId, + required String? clientSecret, + required redirectUri, Iterable scopes = const []}) { if (redirectUri is String) { return ExternalAuthOptions._( @@ -51,8 +50,8 @@ class ExternalAuthOptions { /// * `redirect_uri` factory ExternalAuthOptions.fromMap(Map map) { return ExternalAuthOptions( - clientId: map['client_id'] as String, - clientSecret: map['client_secret'] as String, + clientId: map['client_id'] as String?, + clientSecret: map['client_secret'] as String?, redirectUri: map['redirect_uri'], scopes: map['scopes'] is Iterable ? ((map['scopes'] as Iterable).map((x) => x.toString())) @@ -73,10 +72,10 @@ class ExternalAuthOptions { /// Creates a copy of this object, with the specified changes. ExternalAuthOptions copyWith( - {String clientId, - String clientSecret, + {String? clientId, + String? clientSecret, redirectUri, - Iterable scopes}) { + Iterable? scopes}) { return ExternalAuthOptions( clientId: clientId ?? this.clientId, clientSecret: clientSecret ?? this.clientSecret, @@ -111,14 +110,14 @@ class ExternalAuthOptions { /// If no [asteriskCount] is given, then the number of asterisks will equal the length of /// the actual [clientSecret]. @override - String toString({bool obscureSecret = true, int asteriskCount}) { - String secret; + String toString({bool obscureSecret = true, int? asteriskCount}) { + String? secret; if (!obscureSecret) { secret = clientSecret; } else { var codeUnits = - List.filled(asteriskCount ?? clientSecret.length, $asterisk); + List.filled(asteriskCount ?? clientSecret!.length, $asterisk); secret = String.fromCharCodes(codeUnits); } diff --git a/packages/auth/lib/src/middleware/require_auth.dart b/packages/auth/lib/src/middleware/require_auth.dart index 087ddb27..35b7c164 100644 --- a/packages/auth/lib/src/middleware/require_auth.dart +++ b/packages/auth/lib/src/middleware/require_auth.dart @@ -4,12 +4,12 @@ import 'package:angel_framework/angel_framework.dart'; /// Forces Basic authentication over the requested resource, with the given [realm] name, if no JWT is present. /// /// [realm] defaults to `'angel_auth'`. -RequestHandler forceBasicAuth({String realm}) { +RequestHandler forceBasicAuth({String? realm}) { return (RequestContext req, ResponseContext res) async { - if (req.container.has()) + if (req.container!.has()) { return true; - else if (req.container.has>()) { - await req.container.makeAsync(); + } else if (req.container!.has>()) { + await req.container!.makeAsync(); return true; } @@ -26,16 +26,18 @@ RequestHandler requireAuthentication() { if (throwError) { res.statusCode = 403; throw AngelHttpException.forbidden(); - } else + } else { return false; + } } - if (req.container.has() || req.method == 'OPTIONS') + if (req.container!.has() || req.method == 'OPTIONS') { return true; - else if (req.container.has>()) { - await req.container.makeAsync(); + } else if (req.container!.has>()) { + await req.container!.makeAsync(); return true; - } else + } else { return _reject(res); + } }; } diff --git a/packages/auth/lib/src/options.dart b/packages/auth/lib/src/options.dart index 6b9e9323..1e177f47 100644 --- a/packages/auth/lib/src/options.dart +++ b/packages/auth/lib/src/options.dart @@ -3,17 +3,17 @@ import 'dart:async'; import 'package:angel_framework/angel_framework.dart'; import 'auth_token.dart'; -typedef FutureOr AngelAuthCallback( +typedef AngelAuthCallback = FutureOr Function( RequestContext req, ResponseContext res, String token); -typedef FutureOr AngelAuthTokenCallback( +typedef AngelAuthTokenCallback = FutureOr Function( RequestContext req, ResponseContext res, AuthToken token, User user); class AngelAuthOptions { - AngelAuthCallback callback; - AngelAuthTokenCallback tokenCallback; - String successRedirect; - String failureRedirect; + AngelAuthCallback? callback; + AngelAuthTokenCallback? tokenCallback; + String? successRedirect; + String? failureRedirect; /// If `false` (default: `true`), then successful authentication will return `true` and allow the /// execution of subsequent handlers, just like any other middleware. @@ -26,5 +26,5 @@ class AngelAuthOptions { this.tokenCallback, this.canRespondWithJson = true, this.successRedirect, - String this.failureRedirect}); + this.failureRedirect}); } diff --git a/packages/auth/lib/src/plugin.dart b/packages/auth/lib/src/plugin.dart index 71b4b975..57878eca 100644 --- a/packages/auth/lib/src/plugin.dart +++ b/packages/auth/lib/src/plugin.dart @@ -9,12 +9,12 @@ import 'strategy.dart'; /// Handles authentication within an Angel application. class AngelAuth { - Hmac _hs256; - int _jwtLifeSpan; - final StreamController _onLogin = StreamController(), + Hmac? _hs256; + int? _jwtLifeSpan; + final StreamController _onLogin = StreamController(), _onLogout = StreamController(); - Math.Random _random = Math.Random.secure(); - final RegExp _rgxBearer = RegExp(r"^Bearer"); + final Math.Random _random = Math.Random.secure(); + final RegExp _rgxBearer = RegExp(r'^Bearer'); /// If `true` (default), then JWT's will be stored and retrieved from a `token` cookie. final bool allowCookie; @@ -29,7 +29,7 @@ class AngelAuth { /// A domain to restrict emitted cookies to. /// /// Only applies if [allowCookie] is `true`. - final String cookieDomain; + final String? cookieDomain; /// A path to restrict emitted cookies to. /// @@ -48,19 +48,19 @@ class AngelAuth { Map> strategies = {}; /// Serializes a user into a unique identifier associated only with one identity. - FutureOr Function(User) serializer; + FutureOr Function(User)? serializer; /// Deserializes a unique identifier into its associated identity. In most cases, this is a user object or model instance. - FutureOr Function(Object) deserializer; + FutureOr Function(Object?)? deserializer; /// Fires the result of [deserializer] whenever a user signs in to the application. - Stream get onLogin => _onLogin.stream; + Stream get onLogin => _onLogin.stream; /// Fires `req.user`, which is usually the result of [deserializer], whenever a user signs out of the application. - Stream get onLogout => _onLogout.stream; + Stream get onLogout => _onLogout.stream; /// The [Hmac] being used to encode JWT's. - Hmac get hmac => _hs256; + Hmac? get hmac => _hs256; String _randomString( {int length = 32, @@ -73,10 +73,10 @@ class AngelAuth { /// `jwtLifeSpan` - should be in *milliseconds*. AngelAuth( - {String jwtKey, + {String? jwtKey, this.serializer, this.deserializer, - num jwtLifeSpan, + num? jwtLifeSpan, this.allowCookie = true, this.allowTokenInQuery = true, this.enforceIp = true, @@ -92,21 +92,24 @@ class AngelAuth { /// Configures an Angel server to decode and validate JSON Web tokens on demand, /// whenever an instance of [User] is injected. Future configureServer(Angel app) async { - if (serializer == null) + if (serializer == null) { throw StateError( 'An `AngelAuth` plug-in was called without its `serializer` being set. All authentication will fail.'); - if (deserializer == null) + } + if (deserializer == null) { throw StateError( 'An `AngelAuth` plug-in was called without its `deserializer` being set. All authentication will fail.'); + } - app.container.registerSingleton(this); - if (runtimeType != AngelAuth) - app.container.registerSingleton(this, as: AngelAuth); + app.container!.registerSingleton(this); + if (runtimeType != AngelAuth) { + app.container!.registerSingleton(this, as: AngelAuth); + } - if (!app.container.has<_AuthResult>()) { - app.container + if (!app.container!.has<_AuthResult>()) { + app.container! .registerLazySingleton>>((container) async { - var req = container.make(); + var req = container.make()!; var res = container.make(); var result = await _decodeJwt(req, res); if (result != null) { @@ -116,20 +119,19 @@ class AngelAuth { } }); - app.container.registerLazySingleton>((container) async { - var result = await container.makeAsync<_AuthResult>(); + app.container!.registerLazySingleton>((container) async { + var result = await container.makeAsync<_AuthResult>()!; return result.user; }); - app.container.registerLazySingleton>((container) async { - var result = await container.makeAsync<_AuthResult>(); + app.container! + .registerLazySingleton>((container) async { + var result = await container.makeAsync<_AuthResult>()!; return result.token; }); } - if (reviveTokenEndpoint != null) { - app.post(reviveTokenEndpoint, reviveJwt); - } + app.post(reviveTokenEndpoint, reviveJwt); app.shutdownHooks.add((_) { _onLogin.close(); @@ -137,17 +139,17 @@ class AngelAuth { } void _apply( - RequestContext req, ResponseContext res, AuthToken token, User user) { - if (!req.container.has()) { - req.container.registerSingleton(user); + RequestContext req, ResponseContext? res, AuthToken token, User user) { + if (!req.container!.has()) { + req.container!.registerSingleton(user); } - if (!req.container.has()) { - req.container.registerSingleton(token); + if (!req.container!.has()) { + req.container!.registerSingleton(token); } if (allowCookie == true) { - _addProtectedCookie(res, 'token', token.serialize(_hs256)); + _addProtectedCookie(res!, 'token', token.serialize(_hs256!)); } } @@ -174,7 +176,7 @@ class AngelAuth { /// ``` @deprecated Future decodeJwt(RequestContext req, ResponseContext res) async { - if (req.method == "POST" && req.path == reviveTokenEndpoint) { + if (req.method == 'POST' && req.path == reviveTokenEndpoint) { return await reviveJwt(req, res); } else { await _decodeJwt(req, res); @@ -182,28 +184,30 @@ class AngelAuth { } } - Future<_AuthResult> _decodeJwt( - RequestContext req, ResponseContext res) async { - String jwt = getJwt(req); + Future<_AuthResult?> _decodeJwt( + RequestContext req, ResponseContext? res) async { + var jwt = getJwt(req); if (jwt != null) { - var token = AuthToken.validate(jwt, _hs256); + var token = AuthToken.validate(jwt, _hs256!); if (enforceIp) { - if (req.ip != null && req.ip != token.ipAddress) + if (req.ip != token.ipAddress) { throw AngelHttpException.forbidden( message: "JWT cannot be accessed from this IP address."); + } } - if (token.lifeSpan > -1) { + if (token.lifeSpan! > -1) { var expiry = - token.issuedAt.add(Duration(milliseconds: token.lifeSpan.toInt())); + token.issuedAt.add(Duration(milliseconds: token.lifeSpan!.toInt())); - if (!expiry.isAfter(DateTime.now())) + if (!expiry.isAfter(DateTime.now())) { throw AngelHttpException.forbidden(message: "Expired JWT."); + } } - var user = await deserializer(token.userId); + var user = await deserializer!(token.userId); _apply(req, res, token, user); return _AuthResult(user, token); } @@ -212,19 +216,20 @@ class AngelAuth { } /// Retrieves a JWT from a request, if any was sent at all. - String getJwt(RequestContext req) { - if (req.headers.value("Authorization") != null) { - final authHeader = req.headers.value("Authorization"); + String? getJwt(RequestContext req) { + if (req.headers!.value("Authorization") != null) { + final authHeader = req.headers!.value("Authorization")!; // Allow Basic auth to fall through - if (_rgxBearer.hasMatch(authHeader)) + if (_rgxBearer.hasMatch(authHeader)) { return authHeader.replaceAll(_rgxBearer, "").trim(); + } } else if (allowCookie && - req.cookies.any((cookie) => cookie.name == "token")) { - return req.cookies.firstWhere((cookie) => cookie.name == "token").value; + req.cookies!.any((cookie) => cookie.name == "token")) { + return req.cookies!.firstWhere((cookie) => cookie.name == "token").value; } else if (allowTokenInQuery && - req.uri.queryParameters['token'] is String) { - return req.uri.queryParameters['token']?.toString(); + req.uri!.queryParameters['token'] is String) { + return req.uri!.queryParameters['token']?.toString(); } return null; @@ -243,10 +248,10 @@ class AngelAuth { cookie.secure = true; } - if (_jwtLifeSpan > 0) { - cookie.maxAge ??= _jwtLifeSpan < 0 ? -1 : _jwtLifeSpan ~/ 1000; + if (_jwtLifeSpan! > 0) { + cookie.maxAge ??= _jwtLifeSpan! < 0 ? -1 : _jwtLifeSpan! ~/ 1000; cookie.expires ??= - DateTime.now().add(Duration(milliseconds: _jwtLifeSpan)); + DateTime.now().add(Duration(milliseconds: _jwtLifeSpan!)); } cookie.domain ??= cookieDomain; @@ -261,22 +266,23 @@ class AngelAuth { var jwt = getJwt(req); if (jwt == null) { - var body = await req.parseBody().then((_) => req.bodyAsMap); + var body = await req.parseBody().then((_) => req.bodyAsMap!); jwt = body['token']?.toString(); } if (jwt == null) { throw AngelHttpException.forbidden(message: "No JWT provided"); } else { - var token = AuthToken.validate(jwt, _hs256); + var token = AuthToken.validate(jwt, _hs256!); if (enforceIp) { - if (req.ip != token.ipAddress) + if (req.ip != token.ipAddress) { throw AngelHttpException.forbidden( message: "JWT cannot be accessed from this IP address."); + } } - if (token.lifeSpan > -1) { + if (token.lifeSpan! > -1) { var expiry = token.issuedAt - .add(Duration(milliseconds: token.lifeSpan.toInt())); + .add(Duration(milliseconds: token.lifeSpan!.toInt())); if (!expiry.isAfter(DateTime.now())) { //print( @@ -287,11 +293,11 @@ class AngelAuth { } if (allowCookie) { - _addProtectedCookie(res, 'token', token.serialize(_hs256)); + _addProtectedCookie(res, 'token', token.serialize(_hs256!)); } - final data = await deserializer(token.userId); - return {'data': data, 'token': token.serialize(_hs256)}; + final data = await deserializer!(token.userId); + return {'data': data, 'token': token.serialize(_hs256!)}; } } catch (e) { if (e is AngelHttpException) rethrow; @@ -307,14 +313,14 @@ class AngelAuth { /// or a `401 Not Authenticated` is thrown, if it is the last one. /// /// Any other result is considered an authenticated user, and terminates the loop. - RequestHandler authenticate(type, [AngelAuthOptions options]) { + RequestHandler authenticate(type, [AngelAuthOptions? options]) { return (RequestContext req, ResponseContext res) async { - List names = []; + var names = []; var arr = type is Iterable ? type.map((x) => x.toString()).toList() : [type.toString()]; - for (String t in arr) { + for (var t in arr) { var n = t .split(',') .map((s) => s.trim()) @@ -323,34 +329,34 @@ class AngelAuth { names.addAll(n); } - for (int i = 0; i < names.length; i++) { + for (var i = 0; i < names.length; i++) { var name = names[i]; var strategy = strategies[name] ??= throw ArgumentError('No strategy "$name" found.'); - var hasExisting = req.container.has(); + var hasExisting = req.container!.has(); var result = hasExisting - ? req.container.make() - : await strategy.authenticate(req, res, options); - if (result == true) + ? req.container!.make() + : await strategy.authenticate(req, res, options!); + if (result == true) { return result; - else if (result != false && result != null) { - var userId = await serializer(result); + } else if (result != false && result != null) { + var userId = await serializer!(result); // Create JWT var token = AuthToken( userId: userId, lifeSpan: _jwtLifeSpan, ipAddress: req.ip); - var jwt = token.serialize(_hs256); + var jwt = token.serialize(_hs256!); if (options?.tokenCallback != null) { - if (!req.container.has()) { - req.container.registerSingleton(result); + if (!req.container!.has()) { + req.container!.registerSingleton(result); } - var r = await options.tokenCallback(req, res, token, result); + var r = await options!.tokenCallback!(req, res, token, result); if (r != null) return r; - jwt = token.serialize(_hs256); + jwt = token.serialize(_hs256!); } _apply(req, res, token, result); @@ -360,17 +366,17 @@ class AngelAuth { } if (options?.callback != null) { - return await options.callback(req, res, jwt); + return await options!.callback!(req, res, jwt); } if (options?.successRedirect?.isNotEmpty == true) { - await res.redirect(options.successRedirect); + await res.redirect(options!.successRedirect); return false; } else if (options?.canRespondWithJson != false && req.accepts('application/json')) { var user = hasExisting ? result - : await deserializer(await serializer(result)); + : await deserializer!(await serializer!(result)); _onLogin.add(user); return {"data": user, "token": jwt}; } @@ -381,13 +387,14 @@ class AngelAuth { // Check if not redirect if (res.statusCode == 301 || res.statusCode == 302 || - res.headers.containsKey('location')) + res.headers.containsKey('location')) { return false; - else if (options?.failureRedirect != null) { - await res.redirect(options.failureRedirect); + } else if (options?.failureRedirect != null) { + await res.redirect(options!.failureRedirect); return false; - } else + } else { throw AngelHttpException.notAuthenticated(); + } } } }; @@ -395,33 +402,33 @@ class AngelAuth { /// Log a user in on-demand. Future login(AuthToken token, RequestContext req, ResponseContext res) async { - var user = await deserializer(token.userId); + var user = await deserializer!(token.userId); _apply(req, res, token, user); _onLogin.add(user); if (allowCookie) { - _addProtectedCookie(res, 'token', token.serialize(_hs256)); + _addProtectedCookie(res, 'token', token.serialize(_hs256!)); } } /// Log a user in on-demand. Future loginById(userId, RequestContext req, ResponseContext res) async { - var user = await deserializer(userId); + var user = await deserializer!(userId); var token = AuthToken(userId: userId, lifeSpan: _jwtLifeSpan, ipAddress: req.ip); _apply(req, res, token, user); _onLogin.add(user); if (allowCookie) { - _addProtectedCookie(res, 'token', token.serialize(_hs256)); + _addProtectedCookie(res, 'token', token.serialize(_hs256!)); } } /// Log an authenticated user out. - RequestHandler logout([AngelAuthOptions options]) { + RequestHandler logout([AngelAuthOptions? options]) { return (RequestContext req, ResponseContext res) async { - if (req.container.has()) { - var user = req.container.make(); + if (req.container!.has()) { + var user = req.container!.make(); _onLogout.add(user); } @@ -432,7 +439,7 @@ class AngelAuth { if (options != null && options.successRedirect != null && - options.successRedirect.isNotEmpty) { + options.successRedirect!.isNotEmpty) { await res.redirect(options.successRedirect); } diff --git a/packages/auth/lib/src/strategies/local.dart b/packages/auth/lib/src/strategies/local.dart index 21eeed2f..eb118512 100644 --- a/packages/auth/lib/src/strategies/local.dart +++ b/packages/auth/lib/src/strategies/local.dart @@ -4,15 +4,16 @@ import 'package:angel_framework/angel_framework.dart'; import '../options.dart'; import '../strategy.dart'; -bool _validateString(String str) => str != null && str.isNotEmpty; +bool _validateString(String? str) => str != null && str.isNotEmpty; /// Determines the validity of an incoming username and password. -typedef FutureOr LocalAuthVerifier( - String username, String password); +// typedef FutureOr LocalAuthVerifier(String? username, String? password); +typedef LocalAuthVerifier = FutureOr Function( + String? username, String? password); class LocalAuthStrategy extends AuthStrategy { - RegExp _rgxBasic = RegExp(r'^Basic (.+)$', caseSensitive: false); - RegExp _rgxUsrPass = RegExp(r'^([^:]+):(.+)$'); + final RegExp _rgxBasic = RegExp(r'^Basic (.+)$', caseSensitive: false); + final RegExp _rgxUsrPass = RegExp(r'^([^:]+):(.+)$'); LocalAuthVerifier verifier; String usernameField; @@ -23,33 +24,32 @@ class LocalAuthStrategy extends AuthStrategy { String realm; LocalAuthStrategy(this.verifier, - {String this.usernameField = 'username', - String this.passwordField = 'password', - String this.invalidMessage = - 'Please provide a valid username and password.', - bool this.allowBasic = true, - bool this.forceBasic = false, - String this.realm = 'Authentication is required.'}); + {this.usernameField = 'username', + this.passwordField = 'password', + this.invalidMessage = 'Please provide a valid username and password.', + this.allowBasic = true, + this.forceBasic = false, + this.realm = 'Authentication is required.'}); @override - Future authenticate(RequestContext req, ResponseContext res, - [AngelAuthOptions options_]) async { - AngelAuthOptions options = options_ ?? AngelAuthOptions(); - User verificationResult; + Future authenticate(RequestContext req, ResponseContext res, + [AngelAuthOptions? options_]) async { + var options = options_ ?? AngelAuthOptions(); + User? verificationResult; if (allowBasic) { - String authHeader = req.headers.value('authorization') ?? ""; + var authHeader = req.headers!.value('authorization') ?? ''; if (_rgxBasic.hasMatch(authHeader)) { - String base64AuthString = _rgxBasic.firstMatch(authHeader).group(1); - String authString = - String.fromCharCodes(base64.decode(base64AuthString)); + var base64AuthString = _rgxBasic.firstMatch(authHeader)!.group(1)!; + var authString = String.fromCharCodes(base64.decode(base64AuthString)); if (_rgxUsrPass.hasMatch(authString)) { - Match usrPassMatch = _rgxUsrPass.firstMatch(authString); + Match usrPassMatch = _rgxUsrPass.firstMatch(authString)!; verificationResult = await verifier(usrPassMatch.group(1), usrPassMatch.group(2)); - } else + } else { throw AngelHttpException.badRequest(errors: [invalidMessage]); + } if (verificationResult == false || verificationResult == null) { res @@ -68,27 +68,29 @@ class LocalAuthStrategy extends AuthStrategy { .parseBody() .then((_) => req.bodyAsMap) .catchError((_) => {}); - if (_validateString(body[usernameField]?.toString()) && - _validateString(body[passwordField]?.toString())) { - verificationResult = await verifier( - body[usernameField]?.toString(), body[passwordField]?.toString()); + if (body != null) { + if (_validateString(body[usernameField]?.toString()) && + _validateString(body[passwordField]?.toString())) { + verificationResult = await verifier( + body[usernameField]?.toString(), body[passwordField]?.toString()); + } } } if (verificationResult == false || verificationResult == null) { if (options.failureRedirect != null && - options.failureRedirect.isNotEmpty) { + options.failureRedirect!.isNotEmpty) { await res.redirect(options.failureRedirect, code: 401); return null; } if (forceBasic) { res.headers['www-authenticate'] = 'Basic realm="$realm"'; - throw AngelHttpException.notAuthenticated(); + return null; } return null; - } else if (verificationResult != null && verificationResult != false) { + } else if (verificationResult != false) { return verificationResult; } else { throw AngelHttpException.notAuthenticated(); diff --git a/packages/auth/lib/src/strategy.dart b/packages/auth/lib/src/strategy.dart index 72073686..a7ec4c70 100644 --- a/packages/auth/lib/src/strategy.dart +++ b/packages/auth/lib/src/strategy.dart @@ -5,6 +5,6 @@ import 'options.dart'; /// A function that handles login and signup for an Angel application. abstract class AuthStrategy { /// Authenticates or rejects an incoming user. - FutureOr authenticate(RequestContext req, ResponseContext res, + FutureOr authenticate(RequestContext req, ResponseContext res, [AngelAuthOptions options]); } diff --git a/packages/auth/pubspec.yaml b/packages/auth/pubspec.yaml index fffbf774..1fdbbe8f 100644 --- a/packages/auth/pubspec.yaml +++ b/packages/auth/pubspec.yaml @@ -1,11 +1,11 @@ name: angel_auth description: A complete authentication plugin for Angel. Includes support for stateless JWT tokens, Basic Auth, and more. -version: 3.0.0 +version: 4.0.0 author: Tobe O homepage: https://github.com/angel-dart/angel_auth publish_to: none environment: - sdk: ">=2.10.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: angel_framework: git: @@ -17,7 +17,7 @@ dependencies: crypto: ^3.0.0 http_parser: ^4.0.0 meta: ^1.3.0 - quiver_hashcode: ^3.0.0+1 + quiver: ^3.0.0 dev_dependencies: http: ^0.13.1 io: ^1.0.0 diff --git a/packages/auth/test/callback_test.dart b/packages/auth/test/callback_test.dart index 2506c12d..25b86d3e 100644 --- a/packages/auth/test/callback_test.dart +++ b/packages/auth/test/callback_test.dart @@ -3,20 +3,22 @@ import 'package:angel_auth/angel_auth.dart'; import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/http.dart'; import 'dart:convert'; +import 'package:collection/collection.dart' show IterableExtension; import 'package:http/http.dart' as http; import 'package:io/ansi.dart'; import 'package:logging/logging.dart'; import 'package:test/test.dart'; +import 'package:collection/collection.dart'; class User extends Model { - String username, password; + String? username, password; User({this.username, this.password}); static User parse(Map map) { return User( - username: map['username'] as String, - password: map['password'] as String, + username: map['username'] as String?, + password: map['password'] as String?, ); } @@ -31,27 +33,27 @@ class User extends Model { } } -main() { - Angel app; - AngelHttp angelHttp; - AngelAuth auth; - http.Client client; +void main() { + Angel? app; + late AngelHttp angelHttp; + AngelAuth auth; + http.Client? client; HttpServer server; - String url; + String? url; setUp(() async { hierarchicalLoggingEnabled = true; app = Angel(); angelHttp = AngelHttp(app); - app.use('/users', MapService()); + app!.use('/users', MapService()); - var oldErrorHandler = app.errorHandler; - app.errorHandler = (e, req, res) { - app.logger.severe(e.message, e, e.stackTrace ?? StackTrace.current); + var oldErrorHandler = app!.errorHandler; + app!.errorHandler = (e, req, res) { + app!.logger!.severe(e.message, e, e.stackTrace ?? StackTrace.current); return oldErrorHandler(e, req, res); }; - app.logger = Logger('angel_auth') + app!.logger = Logger('angel_auth') ..level = Level.FINEST ..onRecord.listen((rec) { print(rec); @@ -65,28 +67,30 @@ main() { } }); - await app - .findService('users') + await app! + .findService('users')! .create({'username': 'jdoe1', 'password': 'password'}); - auth = AngelAuth(); - auth.serializer = (u) => u.id; + auth = AngelAuth(); + auth.serializer = (u) => u!.id; auth.deserializer = - (id) async => await app.findService('users').read(id) as User; + (id) async => await app!.findService('users')!.read(id) as User; - await app.configure(auth.configureServer); + await app!.configure(auth.configureServer); auth.strategies['local'] = LocalAuthStrategy((username, password) async { - var users = await app - .findService('users') + var users = await app! + .findService('users')! .index() .then((it) => it.map((m) => User.parse(m as Map)).toList()); - return users.firstWhere( - (user) => user.username == username && user.password == password, - orElse: () => null); + + var result = users.firstWhereOrNull( + (user) => user.username == username && user.password == password); + + return Future.value(result); }); - app.post( + app!.post( '/login', auth.authenticate('local', AngelAuthOptions(callback: (req, res, token) { @@ -95,10 +99,10 @@ main() { ..close(); }))); - app.chain([ + app!.chain([ (req, res) { - if (!req.container.has()) { - req.container.registerSingleton( + if (!req.container!.has()) { + req.container!.registerSingleton( User(username: req.params['name']?.toString())); } return true; @@ -114,7 +118,7 @@ main() { }); tearDown(() async { - client.close(); + client!.close(); await angelHttp.close(); app = null; client = null; @@ -122,7 +126,7 @@ main() { }); test('login', () async { - final response = await client.post(Uri.parse('$url/login'), + final response = await client!.post(Uri.parse('$url/login'), body: {'username': 'jdoe1', 'password': 'password'}); print('Response: ${response.body}'); expect(response.body, equals('Hello!')); @@ -132,7 +136,7 @@ main() { : null); test('preserve existing user', () async { - final response = await client.post(Uri.parse('$url/existing/foo'), + final response = await client!.post(Uri.parse('$url/existing/foo'), body: {'username': 'jdoe1', 'password': 'password'}, headers: {'accept': 'application/json'}); print('Response: ${response.body}'); diff --git a/packages/auth/test/local_test.dart b/packages/auth/test/local_test.dart index f0239e6e..04e9c655 100644 --- a/packages/auth/test/local_test.dart +++ b/packages/auth/test/local_test.dart @@ -7,17 +7,17 @@ import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; import 'package:test/test.dart'; -final AngelAuth> auth = AngelAuth>(); +final AngelAuth?> auth = AngelAuth?>(); var headers = {'accept': 'application/json'}; var localOpts = AngelAuthOptions>( failureRedirect: '/failure', successRedirect: '/success'); Map sampleUser = {'hello': 'world'}; -Future> verifier(String username, String password) async { +Future> verifier(String? username, String? password) async { if (username == 'username' && password == 'password') { return sampleUser; } else { - return null; + throw ArgumentError('Unexpected type for data'); } } @@ -31,10 +31,10 @@ Future wireAuth(Angel app) async { void main() async { Angel app; - AngelHttp angelHttp; - http.Client client; - String url; - String basicAuthUrl; + late AngelHttp angelHttp; + http.Client? client; + String? url; + String? basicAuthUrl; setUp(() async { client = http.Client(); @@ -72,7 +72,7 @@ void main() async { }); test('can use "auth" as middleware', () async { - var response = await client.get(Uri.parse('$url/success'), + var response = await client!.get(Uri.parse('$url/success'), headers: {'Accept': 'application/json'}); print(response.body); expect(response.statusCode, equals(403)); @@ -80,7 +80,7 @@ void main() async { test('successRedirect', () async { var postData = {'username': 'username', 'password': 'password'}; - var response = await client.post(Uri.parse('$url/login'), + var response = await client!.post(Uri.parse('$url/login'), body: json.encode(postData), headers: {'content-type': 'application/json'}); expect(response.statusCode, equals(302)); @@ -89,7 +89,7 @@ void main() async { test('failureRedirect', () async { var postData = {'username': 'password', 'password': 'username'}; - var response = await client.post(Uri.parse('$url/login'), + var response = await client!.post(Uri.parse('$url/login'), body: json.encode(postData), headers: {'content-type': 'application/json'}); print('Login response: ${response.body}'); @@ -99,13 +99,13 @@ void main() async { test('allow basic', () async { var authString = base64.encode('username:password'.runes.toList()); - var response = await client.get(Uri.parse('$url/hello'), + var response = await client!.get(Uri.parse('$url/hello'), headers: {'authorization': 'Basic $authString'}); expect(response.body, equals('"Woo auth"')); }); test('allow basic via URL encoding', () async { - var response = await client.get(Uri.parse('$basicAuthUrl/hello')); + var response = await client!.get(Uri.parse('$basicAuthUrl/hello')); expect(response.body, equals('"Woo auth"')); }); @@ -113,7 +113,7 @@ void main() async { auth.strategies.clear(); auth.strategies['local'] = LocalAuthStrategy(verifier, forceBasic: true, realm: 'test'); - var response = await client.get(Uri.parse('$url/hello'), headers: { + var response = await client!.get(Uri.parse('$url/hello'), headers: { 'accept': 'application/json', 'content-type': 'application/json' }); diff --git a/packages/auth/test/protect_cookie_test.dart b/packages/auth/test/protect_cookie_test.dart index 0d12f3d1..283af961 100644 --- a/packages/auth/test/protect_cookie_test.dart +++ b/packages/auth/test/protect_cookie_test.dart @@ -6,7 +6,7 @@ import 'package:test/test.dart'; const Duration threeDays = const Duration(days: 3); void main() { - Cookie defaultCookie; + late Cookie defaultCookie; var auth = AngelAuth( secureCookies: true, cookieDomain: 'SECURE', @@ -21,7 +21,7 @@ void main() { test('sets expires', () { var now = DateTime.now(); - var expiry = auth.protectCookie(defaultCookie).expires; + var expiry = auth.protectCookie(defaultCookie).expires!; var diff = expiry.difference(now); expect(diff.inSeconds, threeDays.inSeconds); }); From 1ccc78e3a222bdfd6fc24abddff2a9f61ca1d1bd Mon Sep 17 00:00:00 2001 From: thomashii Date: Sun, 21 Mar 2021 08:02:50 +0800 Subject: [PATCH 015/171] Fixed tests --- packages/combinator/lib/src/combinator/chain.dart | 4 ++-- packages/route/lib/src/grammar.dart | 4 ++-- packages/route/lib/src/routing_result.dart | 12 +++++------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/combinator/lib/src/combinator/chain.dart b/packages/combinator/lib/src/combinator/chain.dart index 8e0552c8..49cc8c1b 100644 --- a/packages/combinator/lib/src/combinator/chain.dart +++ b/packages/combinator/lib/src/combinator/chain.dart @@ -22,8 +22,8 @@ class _Alt extends Parser { return result.successful ? result : result.addErrors([ - new SyntaxError(severity ?? SyntaxErrorSeverity.error, errorMessage, - result.span ?? args.scanner.emptySpan), + new SyntaxError( + severity, errorMessage, result.span ?? args.scanner.emptySpan), ]); } diff --git a/packages/route/lib/src/grammar.dart b/packages/route/lib/src/grammar.dart index e2974c15..951eb438 100644 --- a/packages/route/lib/src/grammar.dart +++ b/packages/route/lib/src/grammar.dart @@ -192,8 +192,8 @@ class WildcardSegment extends RouteSegment { var items = r.value!.cast(); var a = items[0], b = items[1]; return a - ..addAll(b?.params ?? {}) - .._setTail(b?.tail); + ..addAll(b.params ?? {}) + .._setTail(b.tail); }); } } diff --git a/packages/route/lib/src/routing_result.dart b/packages/route/lib/src/routing_result.dart index 5d7c4b2a..03ec4f73 100644 --- a/packages/route/lib/src/routing_result.dart +++ b/packages/route/lib/src/routing_result.dart @@ -26,9 +26,9 @@ class RoutingResult { /// The [RoutingResult] that matched the most specific sub-path. RoutingResult get deepest { - RoutingResult search = this; + var search = this; - while (search?.nested?.isNotEmpty == true) { + while (search.nested?.isNotEmpty == true) { search = search.nested!.first; } @@ -43,9 +43,7 @@ class RoutingResult { /// The handlers at this sub-path. List get handlers { - return [] - ..addAll(shallowRouter!.middleware) - ..addAll(shallowRoute!.handlers!); + return [...shallowRouter!.middleware, ...shallowRoute!.handlers!]; } /// All handlers on this sub-path and its children. @@ -69,7 +67,7 @@ class RoutingResult { /// All parameters on this sub-path and its children. Map get allParams { - final Map params = {}; + final params = {}; void crawl(RoutingResult result) { params.addAll(result.params); @@ -92,6 +90,6 @@ class RoutingResult { this.shallowRoute, this.shallowRouter, required this.tail}) { - this.params.addAll(params ?? {}); + this.params.addAll(params); } } From 3dadedaf203131ff0295f129b7a0173cba3eb3fc Mon Sep 17 00:00:00 2001 From: thomashii Date: Sun, 21 Mar 2021 08:19:16 +0800 Subject: [PATCH 016/171] Fixed type error --- packages/combinator/lib/src/combinator/combinator.dart | 2 +- packages/route/lib/src/route.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/combinator/lib/src/combinator/combinator.dart b/packages/combinator/lib/src/combinator/combinator.dart index 3b6d9b14..e2ad9093 100644 --- a/packages/combinator/lib/src/combinator/combinator.dart +++ b/packages/combinator/lib/src/combinator/combinator.dart @@ -193,7 +193,7 @@ abstract class Parser { var preceding = r.value!.isEmpty ? [] : (r.value![0] == null ? [] : [r.value![0]]); var out = List.from(preceding); - if (r.value![1] != null) out.addAll(r.value![1] as Iterable); + if (r.value![1] != null) out.addAll(r.value![1] as List); return out; }); } diff --git a/packages/route/lib/src/route.dart b/packages/route/lib/src/route.dart index 2989e36a..dd943015 100644 --- a/packages/route/lib/src/route.dart +++ b/packages/route/lib/src/route.dart @@ -14,7 +14,7 @@ class Route { : _routeDefinition = RouteGrammar.routeDefinition .parse(SpanScanner(path.replaceAll(_straySlashes, '')))! .value { - if (_routeDefinition?.segments?.isNotEmpty != true) { + if (_routeDefinition?.segments.isNotEmpty != true) { _parser = match('').map((r) => RouteResult({})); } } From 9c2f0cfa28dd1cad0dfaa3a10bcc32d1a559045e Mon Sep 17 00:00:00 2001 From: thomashii Date: Sun, 21 Mar 2021 08:44:06 +0800 Subject: [PATCH 017/171] Fixed null safety --- .../lib/src/combinator/combinator.dart | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/combinator/lib/src/combinator/combinator.dart b/packages/combinator/lib/src/combinator/combinator.dart index e2ad9093..4c2f2ab4 100644 --- a/packages/combinator/lib/src/combinator/combinator.dart +++ b/packages/combinator/lib/src/combinator/combinator.dart @@ -190,11 +190,18 @@ abstract class Parser { Parser> separatedBy(Parser other) { var suffix = other.then(this).index(1).cast(); return this.then(suffix.star()).map((r) { - var preceding = - r.value!.isEmpty ? [] : (r.value![0] == null ? [] : [r.value![0]]); - var out = List.from(preceding); - if (r.value![1] != null) out.addAll(r.value![1] as List); - return out; + List? v = r.value; + if (v != null) { + var preceding = + v.isEmpty ? [] : (r.value?[0] == null ? [] : [r.value?[0]]); + var out = List.from(preceding); + if (r.value?[1] != null) { + out.addAll(r.value?[1] as List); + } + return out; + } else { + return List.empty(growable: true); + } }); } From 0bf41347928aca854d66d3a8365b625f588a0bac Mon Sep 17 00:00:00 2001 From: thomashii Date: Sun, 21 Mar 2021 08:52:58 +0800 Subject: [PATCH 018/171] Fixed null safety --- packages/combinator/lib/src/combinator/combinator.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/combinator/lib/src/combinator/combinator.dart b/packages/combinator/lib/src/combinator/combinator.dart index 4c2f2ab4..4048d934 100644 --- a/packages/combinator/lib/src/combinator/combinator.dart +++ b/packages/combinator/lib/src/combinator/combinator.dart @@ -196,7 +196,9 @@ abstract class Parser { v.isEmpty ? [] : (r.value?[0] == null ? [] : [r.value?[0]]); var out = List.from(preceding); if (r.value?[1] != null) { - out.addAll(r.value?[1] as List); + (r.value?[1] as List).forEach((element) { + out.add(element); + }); } return out; } else { From de6521158efa3a250619855d7ec0feaf00c282f6 Mon Sep 17 00:00:00 2001 From: thomashii Date: Sun, 21 Mar 2021 08:56:26 +0800 Subject: [PATCH 019/171] Fixed null safety --- packages/combinator/lib/src/combinator/combinator.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/combinator/lib/src/combinator/combinator.dart b/packages/combinator/lib/src/combinator/combinator.dart index 4048d934..10cbaf5d 100644 --- a/packages/combinator/lib/src/combinator/combinator.dart +++ b/packages/combinator/lib/src/combinator/combinator.dart @@ -196,8 +196,8 @@ abstract class Parser { v.isEmpty ? [] : (r.value?[0] == null ? [] : [r.value?[0]]); var out = List.from(preceding); if (r.value?[1] != null) { - (r.value?[1] as List).forEach((element) { - out.add(element); + r.value?[1].forEach((element) { + out.add(element as T); }); } return out; From 7f257d173f9f55ff14ccced25e96f21d0c793e2d Mon Sep 17 00:00:00 2001 From: thomashii Date: Sun, 21 Mar 2021 09:01:38 +0800 Subject: [PATCH 020/171] Fixed null safety --- packages/route/lib/src/grammar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/route/lib/src/grammar.dart b/packages/route/lib/src/grammar.dart index 951eb438..3f970ba7 100644 --- a/packages/route/lib/src/grammar.dart +++ b/packages/route/lib/src/grammar.dart @@ -192,7 +192,7 @@ class WildcardSegment extends RouteSegment { var items = r.value!.cast(); var a = items[0], b = items[1]; return a - ..addAll(b.params ?? {}) + ..addAll(b.params) .._setTail(b.tail); }); } From 64d11cc86d221f1522b09082a7c095cc9258ea6a Mon Sep 17 00:00:00 2001 From: thomashii Date: Sun, 21 Mar 2021 09:18:46 +0800 Subject: [PATCH 021/171] Added default param --- packages/mock_request/lib/src/request.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mock_request/lib/src/request.dart b/packages/mock_request/lib/src/request.dart index 69d83b05..df994f5b 100644 --- a/packages/mock_request/lib/src/request.dart +++ b/packages/mock_request/lib/src/request.dart @@ -51,11 +51,11 @@ class MockHttpRequest String? protocolVersion, String? sessionId, this.certificate, - required this.persistentConnection}) { + this.persistentConnection = true}) { _buf = BytesBuilder(copy: copyBuffer != false); _session = MockHttpSession(id: sessionId ?? 'mock-http-session'); - this.protocolVersion = - protocolVersion?.isNotEmpty == true ? protocolVersion! : '1.1'; + + this.protocolVersion = protocolVersion ?? '1.1'; } @override From f280c8b9bd3c3791525eed33d9a36e9b99192c59 Mon Sep 17 00:00:00 2001 From: thomashii Date: Sun, 21 Mar 2021 09:35:56 +0800 Subject: [PATCH 022/171] Fixed null safety --- packages/mock_request/lib/src/request.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mock_request/lib/src/request.dart b/packages/mock_request/lib/src/request.dart index df994f5b..e79a6795 100644 --- a/packages/mock_request/lib/src/request.dart +++ b/packages/mock_request/lib/src/request.dart @@ -135,12 +135,12 @@ class MockHttpRequest @override void write(Object? obj) { - obj?.toString()?.codeUnits?.forEach(writeCharCode); + obj?.toString().codeUnits.forEach(writeCharCode); } @override void writeAll(Iterable objects, [String separator = '']) { - write(objects.join(separator ?? '')); + write(objects.join(separator)); } @override @@ -231,7 +231,7 @@ class MockHttpRequest @override Future join([String separator = '']) => - _stream.stream.join(separator ?? ''); + _stream.stream.join(separator); @override Future get last => _stream.stream.last; From a7c2a0d3dc406c0092e59b15a142340fa651c4c9 Mon Sep 17 00:00:00 2001 From: thomashii Date: Sun, 21 Mar 2021 09:37:54 +0800 Subject: [PATCH 023/171] Fixed null safety --- packages/mock_request/lib/src/response.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/mock_request/lib/src/response.dart b/packages/mock_request/lib/src/response.dart index 2d344115..b631877d 100644 --- a/packages/mock_request/lib/src/response.dart +++ b/packages/mock_request/lib/src/response.dart @@ -16,8 +16,8 @@ class MockHttpResponse extends Stream> implements HttpResponse { final List cookies = []; @override - HttpConnectionInfo connectionInfo = MockHttpConnectionInfo( - remoteAddress: InternetAddress.anyIPv4); + HttpConnectionInfo connectionInfo = + MockHttpConnectionInfo(remoteAddress: InternetAddress.anyIPv4); /// [copyBuffer] corresponds to `copy` on the [BytesBuilder] constructor. MockHttpResponse( @@ -116,17 +116,17 @@ class MockHttpResponse extends Stream> implements HttpResponse { @override Future redirect(Uri location, {int status = HttpStatus.movedTemporarily}) async { - statusCode = status ?? HttpStatus.movedTemporarily; + statusCode = status; } @override void write(Object? obj) { - obj?.toString()?.codeUnits?.forEach(writeCharCode); + obj?.toString().codeUnits.forEach(writeCharCode); } @override void writeAll(Iterable objects, [String separator = '']) { - write(objects.join(separator ?? '')); + write(objects.join(separator)); } @override From cb56b31d102582c0f91840801017d4766c1a4d63 Mon Sep 17 00:00:00 2001 From: thomashii Date: Sun, 21 Mar 2021 09:40:22 +0800 Subject: [PATCH 024/171] Fixed null safety --- CHANGELOG.md | 8 ++++---- packages/framework/lib/src/core/controller.dart | 6 +++--- packages/framework/lib/src/core/hooked_service.dart | 4 ++-- packages/framework/lib/src/core/hostname_router.dart | 2 -- packages/framework/lib/src/core/injection.dart | 2 +- packages/framework/lib/src/core/metadata.dart | 3 +-- packages/framework/lib/src/core/routable.dart | 7 +++++-- packages/framework/lib/src/core/server.dart | 12 ++++++------ packages/framework/lib/src/http/http.dart | 4 ++-- .../lib/src/http/http_response_context.dart | 6 +++--- .../lib/src/http2/http2_response_context.dart | 6 +++--- 11 files changed, 30 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8f598be..06dbba6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,14 +5,14 @@ * Moved angel_cli to https://github.com/dukefirehawk/cli * Added code_buffer and updated to 2.0.0 * Added combinator and updated to 2.0.0 -* Updated angel_route to 5.0.0 +* Updated angel_route to 5.0.0 * Updated angel_model to 3.0.0 * Updated angel_container to 3.0.0 * Added merge_map and updated to 2.0.0 * Added mock_request and updated to 2.0.0 -* Updated angel_framework to 4.0.0 (Revisit TODO) -* Updated angel_auth to 4.0.0 -* Updated angel_configuration to 4.0.0 (todo) +* Updated angel_framework to 4.0.0 (Revisit TODO, Fix failed tests) +* Updated angel_auth to 4.0.0 (In progress) +* Updated angel_configuration to 4.0.0 (In progress) # 3.0.0 (Non NNBD) * Changed Dart SDK requirements for all packages to ">=2.10.0 <3.0.0" diff --git a/packages/framework/lib/src/core/controller.dart b/packages/framework/lib/src/core/controller.dart index d9b3efb6..d1a83ba6 100644 --- a/packages/framework/lib/src/core/controller.dart +++ b/packages/framework/lib/src/core/controller.dart @@ -128,13 +128,13 @@ class Controller { var injection = preInject(reflectedMethod!, reflector); - if (exposeDecl?.allowNull?.isNotEmpty == true) { - injection.optional?.addAll(exposeDecl.allowNull); + if (exposeDecl.allowNull.isNotEmpty == true) { + injection.optional.addAll(exposeDecl.allowNull); } // If there is no path, reverse-engineer one. var path = exposeDecl.path; - var httpMethod = exposeDecl.method ?? 'GET'; + var httpMethod = exposeDecl.method; if (path == null) { // Try to build a route path by finding all potential // path segments, and then joining them. diff --git a/packages/framework/lib/src/core/hooked_service.dart b/packages/framework/lib/src/core/hooked_service.dart index adb63df0..9b6f404f 100644 --- a/packages/framework/lib/src/core/hooked_service.dart +++ b/packages/framework/lib/src/core/hooked_service.dart @@ -471,7 +471,7 @@ class HookedService> HookedServiceEvent event, [HookedServiceEventListener? callback]) { Future? f; - if (callback != null && event?._canceled != true) { + if (callback != null && event._canceled != true) { f = Future.sync(() => callback(event)); } f ??= Future.value(); @@ -560,7 +560,7 @@ class HookedServiceEventDispatcher> { /// Fires an event, and returns it once it is either canceled, or all listeners have run. Future> _emit( HookedServiceEvent event) { - if (event?._canceled == true || event == null || listeners.isEmpty) { + if (event._canceled == true || listeners.isEmpty) { return Future.value(event); } diff --git a/packages/framework/lib/src/core/hostname_router.dart b/packages/framework/lib/src/core/hostname_router.dart index 00f8900a..c568287c 100644 --- a/packages/framework/lib/src/core/hostname_router.dart +++ b/packages/framework/lib/src/core/hostname_router.dart @@ -45,8 +45,6 @@ class HostnameRouter { }); } - apps ??= {}; - creators ??= {}; apps = _parseMap(apps); creators = _parseMap(creators); var patterns = apps.keys.followedBy(creators.keys).toSet().toList(); diff --git a/packages/framework/lib/src/core/injection.dart b/packages/framework/lib/src/core/injection.dart index 28189c2b..92ef801e 100644 --- a/packages/framework/lib/src/core/injection.dart +++ b/packages/framework/lib/src/core/injection.dart @@ -26,7 +26,7 @@ resolveInjection(requirement, InjectionRequest? injection, RequestContext req, ResponseContext res, bool throwOnUnresolved, [Container? container]) async { var propFromApp; - container ??= req?.container ?? res?.app?.container; + container ??= req.container ?? res.app?.container; if (requirement == RequestContext) { return req; diff --git a/packages/framework/lib/src/core/metadata.dart b/packages/framework/lib/src/core/metadata.dart index 56bb9fee..0012b340 100644 --- a/packages/framework/lib/src/core/metadata.dart +++ b/packages/framework/lib/src/core/metadata.dart @@ -122,8 +122,7 @@ class Parameter { /// Obtains a value for this parameter from a [RequestContext]. getValue(RequestContext req) { if (cookie?.isNotEmpty == true) { - return req.cookies!.firstWhere((c) => c.name == cookie)?.value ?? - defaultValue; + return req.cookies!.firstWhere((c) => c.name == cookie).value; } if (header?.isNotEmpty == true) { return req.headers!.value(header!) ?? defaultValue; diff --git a/packages/framework/lib/src/core/routable.dart b/packages/framework/lib/src/core/routable.dart index c01d4bee..b63a27a5 100644 --- a/packages/framework/lib/src/core/routable.dart +++ b/packages/framework/lib/src/core/routable.dart @@ -24,7 +24,7 @@ RequestHandler chain(Iterable handlers) { Future Function()? runPipeline; for (var handler in handlers) { - if (handler == null) break; + //if (handler == null) break; if (runPipeline == null) { runPipeline = () => Future.sync(() => handler(req, res)); @@ -108,7 +108,10 @@ class Routable extends Router { } final handlerSequence = []; - handlerSequence.addAll(middleware as Iterable? Function(RequestContext, ResponseContext)>? ?? []); + handlerSequence.addAll(middleware as Iterable< + FutureOr? Function( + RequestContext, ResponseContext)>? ?? + []); handlerSequence.addAll(handlers); return super.addRoute(method, path.toString(), handler, diff --git a/packages/framework/lib/src/core/server.dart b/packages/framework/lib/src/core/server.dart index 52c9e157..97ec3efa 100644 --- a/packages/framework/lib/src/core/server.dart +++ b/packages/framework/lib/src/core/server.dart @@ -159,7 +159,7 @@ class Angel extends Routable { 'This route will be ignored, and no requests will ever reach it.'); } - return super.addRoute(method, path, handler, middleware: middleware ?? []); + return super.addRoute(method, path, handler, middleware: middleware); } @override @@ -225,21 +225,21 @@ class Angel extends Routable { _flattened!.dumpTree( callback: callback, - header: header?.isNotEmpty == true + header: header.isNotEmpty == true ? header : (environment.isProduction ? 'Dumping flattened route tree:' : 'Dumping route tree:'), - tab: tab ?? ' '); + tab: tab); } else { super.dumpTree( callback: callback, - header: header?.isNotEmpty == true + header: header.isNotEmpty == true ? header : (environment.isProduction ? 'Dumping flattened route tree:' : 'Dumping route tree:'), - tab: tab ?? ' '); + tab: tab); } } @@ -319,7 +319,7 @@ class Angel extends Routable { /// Runs with DI, and *always* reflects. Prefer [runContained]. Future runReflected(Function handler, RequestContext req, ResponseContext res, [Container? container]) { - container ??= req?.container ?? res?.app?.container; + container ??= req.container ?? res.app?.container; var h = handleContained( handler, _preContained[handler] = preInject(handler, container!.reflector), diff --git a/packages/framework/lib/src/http/http.dart b/packages/framework/lib/src/http/http.dart index 44ab4cdb..d4ea40b1 100644 --- a/packages/framework/lib/src/http/http.dart +++ b/packages/framework/lib/src/http/http.dart @@ -9,11 +9,11 @@ export 'http_response_context.dart'; /// Boots a shared server instance. Use this if launching multiple isolates. Future startShared(address, int port) => - HttpServer.bind(address ?? '127.0.0.1', port ?? 0, shared: true); + HttpServer.bind(address ?? '127.0.0.1', port, shared: true); Future Function(dynamic, int) startSharedSecure( SecurityContext securityContext) { return (address, int port) => HttpServer.bindSecure( - address ?? '127.0.0.1', port ?? 0, securityContext, + address ?? '127.0.0.1', port, securityContext, shared: true); } diff --git a/packages/framework/lib/src/http/http_response_context.dart b/packages/framework/lib/src/http/http_response_context.dart index 5ddc8cb8..6cef2527 100644 --- a/packages/framework/lib/src/http/http_response_context.dart +++ b/packages/framework/lib/src/http/http_response_context.dart @@ -60,9 +60,9 @@ class HttpResponseContext extends ResponseContext { return __allowedEncodings ??= correspondingRequest!.headers! .value('accept-encoding') ?.split(',') - ?.map((s) => s.trim()) - ?.where((s) => s.isNotEmpty) - ?.map((str) { + .map((s) => s.trim()) + .where((s) => s.isNotEmpty) + .map((str) { // Ignore quality specifications in accept-encoding // ex. gzip;q=0.8 if (!str.contains(';')) return str; diff --git a/packages/framework/lib/src/http2/http2_response_context.dart b/packages/framework/lib/src/http2/http2_response_context.dart index 4014bd1b..a5911784 100644 --- a/packages/framework/lib/src/http2/http2_response_context.dart +++ b/packages/framework/lib/src/http2/http2_response_context.dart @@ -119,9 +119,9 @@ class Http2ResponseContext extends ResponseContext { return __allowedEncodings ??= correspondingRequest!.headers! .value('accept-encoding') ?.split(',') - ?.map((s) => s.trim()) - ?.where((s) => s.isNotEmpty) - ?.map((str) { + .map((s) => s.trim()) + .where((s) => s.isNotEmpty) + .map((str) { // Ignore quality specifications in accept-encoding // ex. gzip;q=0.8 if (!str.contains(';')) return str; From 01171d895406a7f3b7920dd917a8bfa09bac6cc4 Mon Sep 17 00:00:00 2001 From: thomashii Date: Wed, 31 Mar 2021 14:33:31 +0800 Subject: [PATCH 025/171] Updated to Null Safety --- .../lib/src/combinator/advance.dart | 2 +- .../combinator/lib/src/combinator/any.dart | 14 +++---- .../combinator/lib/src/combinator/cast.dart | 4 +- .../combinator/lib/src/combinator/chain.dart | 4 +- .../combinator/lib/src/combinator/check.dart | 2 +- .../lib/src/combinator/combinator.dart | 26 ++++++------ .../lib/src/combinator/compare.dart | 2 +- .../lib/src/combinator/fold_errors.dart | 2 +- .../combinator/lib/src/combinator/index.dart | 2 +- .../lib/src/combinator/longest.dart | 4 +- .../combinator/lib/src/combinator/map.dart | 6 +-- .../lib/src/combinator/max_depth.dart | 2 +- .../combinator/lib/src/combinator/negate.dart | 2 +- .../combinator/lib/src/combinator/opt.dart | 4 +- .../combinator/lib/src/combinator/reduce.dart | 2 +- .../lib/src/combinator/reference.dart | 12 ++++-- .../combinator/lib/src/combinator/repeat.dart | 40 ++++++++++--------- .../combinator/lib/src/combinator/safe.dart | 21 +++++----- .../lib/src/combinator/to_list.dart | 2 +- .../combinator/lib/src/combinator/value.dart | 2 +- packages/combinator/test/list_test.dart | 6 +-- packages/combinator/test/match_test.dart | 6 +-- packages/combinator/test/misc_test.dart | 24 +++++------ packages/combinator/test/value_test.dart | 4 +- 24 files changed, 101 insertions(+), 94 deletions(-) diff --git a/packages/combinator/lib/src/combinator/advance.dart b/packages/combinator/lib/src/combinator/advance.dart index 49fcdb88..01506dc2 100644 --- a/packages/combinator/lib/src/combinator/advance.dart +++ b/packages/combinator/lib/src/combinator/advance.dart @@ -8,7 +8,7 @@ class _Advance extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth())!.change(parser: this); + var result = parser._parse(args.increaseDepth()).change(parser: this); if (result.successful) args.scanner.position += amount; return result; } diff --git a/packages/combinator/lib/src/combinator/any.dart b/packages/combinator/lib/src/combinator/any.dart index 0c16a81d..616bf29d 100644 --- a/packages/combinator/lib/src/combinator/any.dart +++ b/packages/combinator/lib/src/combinator/any.dart @@ -8,7 +8,7 @@ part of lex.src.combinator; /// generate any error at all. Parser any(Iterable> parsers, {bool backtrack: true, errorMessage, SyntaxErrorSeverity? severity}) { - return new _Any(parsers, backtrack != false, errorMessage, + return _Any(parsers, backtrack != false, errorMessage, severity ?? SyntaxErrorSeverity.error); } @@ -26,14 +26,14 @@ class _Any extends Parser { .where((p) => !args.trampoline.isActive(p, args.scanner.position)); if (inactive.isEmpty) { - return new ParseResult(args.trampoline, args.scanner, this, false, []); + return ParseResult(args.trampoline, args.scanner, this, false, []); } var errors = []; int replay = args.scanner.position; for (var parser in inactive) { - var result = parser._parse(args.increaseDepth())!; + var result = parser._parse(args.increaseDepth()); if (result.successful) return result; @@ -45,7 +45,7 @@ class _Any extends Parser { if (errorMessage != false) { errors.add( - new SyntaxError( + SyntaxError( severity, errorMessage?.toString() ?? 'No match found for ${parsers.length} alternative(s)', @@ -54,13 +54,13 @@ class _Any extends Parser { ); } - return new ParseResult(args.trampoline, args.scanner, this, false, errors); + return ParseResult(args.trampoline, args.scanner, this, false, errors); } @override - ParseResult? __parse(ParseArgs args) { + ParseResult __parse(ParseArgs args) { // Never called - return null; + throw ArgumentError("[Combinator] Invalid method call"); } @override diff --git a/packages/combinator/lib/src/combinator/cast.dart b/packages/combinator/lib/src/combinator/cast.dart index 6bd44f80..bc7280ad 100644 --- a/packages/combinator/lib/src/combinator/cast.dart +++ b/packages/combinator/lib/src/combinator/cast.dart @@ -7,7 +7,7 @@ class _Cast extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth())!; + var result = parser._parse(args.increaseDepth()); return new ParseResult( args.trampoline, args.scanner, @@ -38,7 +38,7 @@ class _CastDynamic extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth())!; + var result = parser._parse(args.increaseDepth()); return new ParseResult( args.trampoline, args.scanner, diff --git a/packages/combinator/lib/src/combinator/chain.dart b/packages/combinator/lib/src/combinator/chain.dart index 49cc8c1b..16af1658 100644 --- a/packages/combinator/lib/src/combinator/chain.dart +++ b/packages/combinator/lib/src/combinator/chain.dart @@ -18,7 +18,7 @@ class _Alt extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth())!; + var result = parser._parse(args.increaseDepth()); return result.successful ? result : result.addErrors([ @@ -48,7 +48,7 @@ class _Chain extends ListParser { bool successful = true; for (var parser in parsers) { - var result = parser._parse(args.increaseDepth())!; + var result = parser._parse(args.increaseDepth()); if (!result.successful) { if (parser is _Alt) errors.addAll(result.errors); diff --git a/packages/combinator/lib/src/combinator/check.dart b/packages/combinator/lib/src/combinator/check.dart index 58416040..a9bfcc63 100644 --- a/packages/combinator/lib/src/combinator/check.dart +++ b/packages/combinator/lib/src/combinator/check.dart @@ -11,7 +11,7 @@ class _Check extends Parser { @override ParseResult __parse(ParseArgs args) { var matchState = {}; - var result = parser._parse(args.increaseDepth())!.change(parser: this); + var result = parser._parse(args.increaseDepth()).change(parser: this); if (!result.successful) return result; else if (!matcher.matches(result.value, matchState)) { diff --git a/packages/combinator/lib/src/combinator/combinator.dart b/packages/combinator/lib/src/combinator/combinator.dart index 10cbaf5d..08e1349e 100644 --- a/packages/combinator/lib/src/combinator/combinator.dart +++ b/packages/combinator/lib/src/combinator/combinator.dart @@ -67,9 +67,9 @@ class ParseArgs { /// A parser combinator, which can parse very complicated grammars in a manageable manner. abstract class Parser { - ParseResult? __parse(ParseArgs args); + ParseResult __parse(ParseArgs args); - ParseResult? _parse(ParseArgs args) { + ParseResult _parse(ParseArgs args) { var pos = args.scanner.position; if (args.trampoline.hasMemoized(this, pos)) @@ -86,7 +86,7 @@ abstract class Parser { } /// Parses text from a [SpanScanner]. - ParseResult? parse(SpanScanner scanner, [int depth = 1]) { + ParseResult parse(SpanScanner scanner, [int depth = 1]) { var args = ParseArgs(Trampoline(), scanner, depth); return _parse(args); } @@ -105,7 +105,7 @@ abstract class Parser { // TODO: Type issue /// Runs the given function, which changes the returned [ParseResult] into one relating to a [U] object. - Parser change(ParseResult Function(ParseResult?) f) { + Parser change(ParseResult Function(ParseResult) f) { return _Change(this, f); } @@ -140,8 +140,10 @@ abstract class Parser { /// Ensures this pattern is not matched. /// /// You can provide an [errorMessage]. - Parser negate({String? errorMessage, SyntaxErrorSeverity? severity}) => - _Negate(this, errorMessage, severity ?? SyntaxErrorSeverity.error); + Parser negate( + {String errorMessage = 'Negate error', + SyntaxErrorSeverity severity = SyntaxErrorSeverity.error}) => + _Negate(this, errorMessage, severity); /// Caches the results of parse attempts at various locations within the source text. /// @@ -153,7 +155,7 @@ abstract class Parser { /// Consumes `this` and another parser, but only considers the result of `this` parser. Parser and(Parser other) => then(other).change((r) { return ParseResult( - r!.trampoline, + r.trampoline, r.scanner, this, r.successful, @@ -176,7 +178,7 @@ abstract class Parser { /// The generated parser only runs once; repeated uses always exit eagerly. Parser safe( {bool backtrack: true, - String? errorMessage, + String errorMessage = "error", SyntaxErrorSeverity? severity}) => _Safe( this, backtrack, errorMessage, severity ?? SyntaxErrorSeverity.error); @@ -240,7 +242,7 @@ abstract class Parser { Parser space() => trail(RegExp(r'[ \n\r\t]+')); /// Consumes 0 or more instance(s) of this parser. - ListParser star({bool backtrack: true}) => + ListParser star({bool backtrack: true}) => times(1, exact: false, backtrack: backtrack).opt(); /// Shortcut for [chain]-ing two parsers together. @@ -259,10 +261,10 @@ abstract class Parser { /// an infinite amount of occurrences after the specified [count]. /// /// You can provide custom error messages for when there are [tooFew] or [tooMany] occurrences. - ListParser times(int count, + ListParser times(int count, {bool exact: true, - String? tooFew, - String? tooMany, + String tooFew = 'Too few', + String tooMany = 'Too many', bool backtrack: true, SyntaxErrorSeverity? severity}) { return _Repeat(this, count, exact, tooFew, tooMany, backtrack, diff --git a/packages/combinator/lib/src/combinator/compare.dart b/packages/combinator/lib/src/combinator/compare.dart index 49a39701..44a1ddd2 100644 --- a/packages/combinator/lib/src/combinator/compare.dart +++ b/packages/combinator/lib/src/combinator/compare.dart @@ -8,7 +8,7 @@ class _Compare extends ListParser { @override ParseResult> __parse(ParseArgs args) { - ParseResult> result = parser._parse(args.increaseDepth())!; + ParseResult> result = parser._parse(args.increaseDepth()); if (!result.successful) return result; result = result.change( diff --git a/packages/combinator/lib/src/combinator/fold_errors.dart b/packages/combinator/lib/src/combinator/fold_errors.dart index 5b838e6f..6d15c69a 100644 --- a/packages/combinator/lib/src/combinator/fold_errors.dart +++ b/packages/combinator/lib/src/combinator/fold_errors.dart @@ -8,7 +8,7 @@ class _FoldErrors extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth())!.change(parser: this); + var result = parser._parse(args.increaseDepth()).change(parser: this); var errors = result.errors.fold>([], (out, e) { if (!out.any((b) => equal(e, b))) out.add(e); return out; diff --git a/packages/combinator/lib/src/combinator/index.dart b/packages/combinator/lib/src/combinator/index.dart index a3791333..6eb9eaf8 100644 --- a/packages/combinator/lib/src/combinator/index.dart +++ b/packages/combinator/lib/src/combinator/index.dart @@ -8,7 +8,7 @@ class _Index extends Parser { @override ParseResult __parse(ParseArgs args) { - ParseResult> result = parser._parse(args.increaseDepth())!; + ParseResult> result = parser._parse(args.increaseDepth()); Object? value; if (result.successful) diff --git a/packages/combinator/lib/src/combinator/longest.dart b/packages/combinator/lib/src/combinator/longest.dart index ae00c2c8..7028bd16 100644 --- a/packages/combinator/lib/src/combinator/longest.dart +++ b/packages/combinator/lib/src/combinator/longest.dart @@ -31,7 +31,7 @@ class _Longest extends Parser { var results = >[]; for (var parser in inactive) { - var result = parser._parse(args.increaseDepth())!; + var result = parser._parse(args.increaseDepth()); if (result.successful && result.span != null) results.add(result); @@ -66,7 +66,7 @@ class _Longest extends Parser { var results = >[]; for (var parser in parsers) { - var result = parser._parse(args.increaseDepth())!; + var result = parser._parse(args.increaseDepth()); if (result.successful) results.add(result); diff --git a/packages/combinator/lib/src/combinator/map.dart b/packages/combinator/lib/src/combinator/map.dart index 37acddd5..4dfb326d 100644 --- a/packages/combinator/lib/src/combinator/map.dart +++ b/packages/combinator/lib/src/combinator/map.dart @@ -8,8 +8,8 @@ class _Map extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth())!; - return new ParseResult( + var result = parser._parse(args.increaseDepth()); + return ParseResult( args.trampoline, args.scanner, this, @@ -34,7 +34,7 @@ class _Map extends Parser { class _Change extends Parser { final Parser parser; - final ParseResult Function(ParseResult?) f; + final ParseResult Function(ParseResult) f; _Change(this.parser, this.f); diff --git a/packages/combinator/lib/src/combinator/max_depth.dart b/packages/combinator/lib/src/combinator/max_depth.dart index f9054132..1c28a9d2 100644 --- a/packages/combinator/lib/src/combinator/max_depth.dart +++ b/packages/combinator/lib/src/combinator/max_depth.dart @@ -7,7 +7,7 @@ class _MaxDepth extends Parser { _MaxDepth(this.parser, this.cap); @override - ParseResult? __parse(ParseArgs args) { + ParseResult __parse(ParseArgs args) { if (args.depth > cap) { return new ParseResult(args.trampoline, args.scanner, this, false, []); } diff --git a/packages/combinator/lib/src/combinator/negate.dart b/packages/combinator/lib/src/combinator/negate.dart index 30a8c377..d49582d8 100644 --- a/packages/combinator/lib/src/combinator/negate.dart +++ b/packages/combinator/lib/src/combinator/negate.dart @@ -9,7 +9,7 @@ class _Negate extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth())!.change(parser: this); + var result = parser._parse(args.increaseDepth()).change(parser: this); if (!result.successful) { return new ParseResult( diff --git a/packages/combinator/lib/src/combinator/opt.dart b/packages/combinator/lib/src/combinator/opt.dart index ac2b31c2..12083ed7 100644 --- a/packages/combinator/lib/src/combinator/opt.dart +++ b/packages/combinator/lib/src/combinator/opt.dart @@ -9,7 +9,7 @@ class _Opt extends Parser { @override ParseResult __parse(ParseArgs args) { var replay = args.scanner.position; - var result = parser._parse(args.increaseDepth())!; + var result = parser._parse(args.increaseDepth()); if (!result.successful) args.scanner.position = replay; @@ -37,7 +37,7 @@ class _ListOpt extends ListParser { @override ParseResult> __parse(ParseArgs args) { var replay = args.scanner.position; - ParseResult> result = parser._parse(args.increaseDepth())!; + ParseResult> result = parser._parse(args.increaseDepth()); if (!result.successful) args.scanner.position = replay; diff --git a/packages/combinator/lib/src/combinator/reduce.dart b/packages/combinator/lib/src/combinator/reduce.dart index ae2d7d14..951da47b 100644 --- a/packages/combinator/lib/src/combinator/reduce.dart +++ b/packages/combinator/lib/src/combinator/reduce.dart @@ -8,7 +8,7 @@ class _Reduce extends Parser { @override ParseResult __parse(ParseArgs args) { - ParseResult> result = parser._parse(args.increaseDepth())!; + ParseResult> result = parser._parse(args.increaseDepth()); if (!result.successful) return new ParseResult( diff --git a/packages/combinator/lib/src/combinator/reference.dart b/packages/combinator/lib/src/combinator/reference.dart index dd2b6b9b..aefa32d3 100644 --- a/packages/combinator/lib/src/combinator/reference.dart +++ b/packages/combinator/lib/src/combinator/reference.dart @@ -16,22 +16,26 @@ class Reference extends Parser { } @override - ParseResult? __parse(ParseArgs args) { + ParseResult __parse(ParseArgs args) { if (_parser == null) throw new StateError('There is no parser assigned to this reference.'); return _parser!._parse(args); } @override - ParseResult? _parse(ParseArgs args) { + ParseResult _parse(ParseArgs args) { + if (_parser == null) + throw new StateError('There is no parser assigned to this reference.'); return _parser!._parse(args); } @override void stringify(CodeBuffer buffer) { - if (_parser == null) + if (_parser == null) { buffer.writeln('(undefined reference <$T>)'); - else if (!printed) _parser!.stringify(buffer); + } else if (!printed) { + _parser!.stringify(buffer); + } printed = true; buffer.writeln('(previously printed reference)'); } diff --git a/packages/combinator/lib/src/combinator/repeat.dart b/packages/combinator/lib/src/combinator/repeat.dart index be8b9c57..2ee452bd 100644 --- a/packages/combinator/lib/src/combinator/repeat.dart +++ b/packages/combinator/lib/src/combinator/repeat.dart @@ -1,63 +1,67 @@ part of lex.src.combinator; -class _Repeat extends ListParser { +class _Repeat extends ListParser { final Parser parser; final int count; final bool exact, backtrack; - final String? tooFew, tooMany; + final String tooFew; + final String tooMany; final SyntaxErrorSeverity severity; _Repeat(this.parser, this.count, this.exact, this.tooFew, this.tooMany, this.backtrack, this.severity); @override - ParseResult> __parse(ParseArgs args) { + ParseResult> __parse(ParseArgs args) { var errors = []; - var results = []; - var spans = []; + var results = []; + var spans = []; int success = 0, replay = args.scanner.position; ParseResult? result; do { result = parser._parse(args.increaseDepth()); - if (result!.successful) { + if (result.successful) { success++; - results.add(result.value); + if (result.value != null) { + results.add(result.value!); + } replay = args.scanner.position; } else if (backtrack) args.scanner.position = replay; - if (result.span != null) spans.add(result.span); + if (result.span != null) { + spans.add(result.span!); + } } while (result.successful); if (success < count) { errors.addAll(result.errors); errors.add( - new SyntaxError( + SyntaxError( severity, - tooFew ?? 'Expected at least $count occurence(s).', + tooFew, result.span ?? args.scanner.emptySpan, ), ); if (backtrack) args.scanner.position = replay; - return new ParseResult>( + return ParseResult>( args.trampoline, args.scanner, this, false, errors); } else if (success > count && exact) { if (backtrack) args.scanner.position = replay; - return new ParseResult>( - args.trampoline, args.scanner, this, false, [ - new SyntaxError( + return ParseResult>(args.trampoline, args.scanner, this, false, [ + SyntaxError( severity, - tooMany ?? 'Expected no more than $count occurence(s).', + tooMany, result.span ?? args.scanner.emptySpan, ), ]); } - var span = spans.reduce((a, b) => a!.expand(b!)); - return new ParseResult>( + var span = spans.reduce((a, b) => a.expand(b)); + return ParseResult>( args.trampoline, args.scanner, this, @@ -70,7 +74,7 @@ class _Repeat extends ListParser { @override void stringify(CodeBuffer buffer) { - var r = new StringBuffer('{$count'); + var r = StringBuffer('{$count'); if (!exact) r.write(','); r.write('}'); buffer diff --git a/packages/combinator/lib/src/combinator/safe.dart b/packages/combinator/lib/src/combinator/safe.dart index 8bac100e..6820d3ee 100644 --- a/packages/combinator/lib/src/combinator/safe.dart +++ b/packages/combinator/lib/src/combinator/safe.dart @@ -3,14 +3,14 @@ part of lex.src.combinator; class _Safe extends Parser { final Parser parser; final bool backtrack; - final String? errorMessage; + final String errorMessage; final SyntaxErrorSeverity severity; bool _triggered = false; _Safe(this.parser, this.backtrack, this.errorMessage, this.severity); @override - ParseResult? __parse(ParseArgs args) { + ParseResult __parse(ParseArgs args) { var replay = args.scanner.position; try { @@ -21,16 +21,13 @@ class _Safe extends Parser { if (backtrack) args.scanner.position = replay; var errors = []; - if (errorMessage != null) { - // TODO: Custom severity for all errors? - errors.add( - new SyntaxError( - severity, - errorMessage, - args.scanner.lastSpan ?? args.scanner.emptySpan, - ), - ); - } + errors.add( + new SyntaxError( + severity, + errorMessage, + args.scanner.lastSpan ?? args.scanner.emptySpan, + ), + ); return new ParseResult( args.trampoline, args.scanner, this, false, errors); diff --git a/packages/combinator/lib/src/combinator/to_list.dart b/packages/combinator/lib/src/combinator/to_list.dart index a339db50..a73fa954 100644 --- a/packages/combinator/lib/src/combinator/to_list.dart +++ b/packages/combinator/lib/src/combinator/to_list.dart @@ -7,7 +7,7 @@ class _ToList extends ListParser { @override ParseResult> __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth())!; + var result = parser._parse(args.increaseDepth()); if (result.value is List) { return (result as ParseResult>).change(parser: this); diff --git a/packages/combinator/lib/src/combinator/value.dart b/packages/combinator/lib/src/combinator/value.dart index ab206187..b0612073 100644 --- a/packages/combinator/lib/src/combinator/value.dart +++ b/packages/combinator/lib/src/combinator/value.dart @@ -8,7 +8,7 @@ class _Value extends Parser { @override ParseResult __parse(ParseArgs args) { - var result = parser._parse(args.increaseDepth())!.change(parser: this); + var result = parser._parse(args.increaseDepth()).change(parser: this); return result.successful ? result.change(value: f(result)) : result; } diff --git a/packages/combinator/test/list_test.dart b/packages/combinator/test/list_test.dart index 64bf8fba..26b0333e 100644 --- a/packages/combinator/test/list_test.dart +++ b/packages/combinator/test/list_test.dart @@ -12,11 +12,11 @@ main() { test('sort', () { var parser = numbers.sort((a, b) => a!.compareTo(b!)); - expect(parser.parse(scan('21,2,3,34,20'))!.value, [2, 3, 20, 21, 34]); + expect(parser.parse(scan('21,2,3,34,20')).value, [2, 3, 20, 21, 34]); }); test('reduce', () { var parser = numbers.reduce((a, b) => a! + b!); - expect(parser.parse(scan('21,2,3,34,20'))!.value, 80); - expect(parser.parse(scan('not numbers'))!.value, isNull); + expect(parser.parse(scan('21,2,3,34,20')).value, 80); + expect(parser.parse(scan('not numbers')).value, isNull); }); } diff --git a/packages/combinator/test/match_test.dart b/packages/combinator/test/match_test.dart index d1406276..60504d93 100644 --- a/packages/combinator/test/match_test.dart +++ b/packages/combinator/test/match_test.dart @@ -4,13 +4,13 @@ import 'common.dart'; main() { test('match string', () { - expect(match('hello').parse(scan('hello world'))!.successful, isTrue); + expect(match('hello').parse(scan('hello world')).successful, isTrue); }); test('match start only', () { - expect(match('hello').parse(scan('goodbye hello'))!.successful, isFalse); + expect(match('hello').parse(scan('goodbye hello')).successful, isFalse); }); test('fail if no match', () { - expect(match('hello').parse(scan('world'))!.successful, isFalse); + expect(match('hello').parse(scan('world')).successful, isFalse); }); } diff --git a/packages/combinator/test/misc_test.dart b/packages/combinator/test/misc_test.dart index f90d5d83..b3a141f9 100644 --- a/packages/combinator/test/misc_test.dart +++ b/packages/combinator/test/misc_test.dart @@ -14,29 +14,29 @@ main() { }); test('change', () { - var parser = match('hello').change((r) => r!.change(value: 23)); - expect(parser.parse(scan('helloworld'))!.value, 23); + var parser = match('hello').change((r) => r.change(value: 23)); + expect(parser.parse(scan('helloworld')).value, 23); }); test('check', () { var parser = match(new RegExp(r'[A-Za-z]+')) .value((r) => r.span!.length) .check(greaterThan(3)); - expect(parser.parse(scan('helloworld'))!.successful, isTrue); - expect(parser.parse(scan('yo'))!.successful, isFalse); + expect(parser.parse(scan('helloworld')).successful, isTrue); + expect(parser.parse(scan('yo')).successful, isFalse); }); test('map', () { var parser = match(new RegExp(r'[A-Za-z]+')).map((r) => r.span!.length); - expect(parser.parse(scan('hello'))!.value, 5); + expect(parser.parse(scan('hello')).value, 5); }); test('negate', () { var parser = match('hello').negate(errorMessage: 'world'); - expect(parser.parse(scan('goodbye world'))!.successful, isTrue); - expect(parser.parse(scan('hello world'))!.successful, isFalse); - expect(parser.parse(scan('hello world'))!.errors.first.message, 'world'); + expect(parser.parse(scan('goodbye world')).successful, isTrue); + expect(parser.parse(scan('hello world')).successful, isFalse); + expect(parser.parse(scan('hello world')).errors.first.message, 'world'); }); group('opt', () { @@ -44,13 +44,13 @@ main() { var list = match('hel').then(match('lo')).opt(); test('succeeds if present', () { - expect(single.parse(scan('hello'))!.successful, isTrue); - expect(list.parse(scan('hello'))!.successful, isTrue); + expect(single.parse(scan('hello')).successful, isTrue); + expect(list.parse(scan('hello')).successful, isTrue); }); test('succeeds if not present', () { - expect(single.parse(scan('goodbye'))!.successful, isTrue); - expect(list.parse(scan('goodbye'))!.successful, isTrue); + expect(single.parse(scan('goodbye')).successful, isTrue); + expect(list.parse(scan('goodbye')).successful, isTrue); }); test('backtracks if not present', () { diff --git a/packages/combinator/test/value_test.dart b/packages/combinator/test/value_test.dart index 97ab3543..c987d10f 100644 --- a/packages/combinator/test/value_test.dart +++ b/packages/combinator/test/value_test.dart @@ -6,10 +6,10 @@ main() { var parser = match('hello').value((r) => 'world'); test('sets value', () { - expect(parser.parse(scan('hello world'))!.value, 'world'); + expect(parser.parse(scan('hello world')).value, 'world'); }); test('no value if no match', () { - expect(parser.parse(scan('goodbye world'))!.value, isNull); + expect(parser.parse(scan('goodbye world')).value, isNull); }); } From ddbf7f33b6a13b0d3d9682d485f8388296a940fe Mon Sep 17 00:00:00 2001 From: thomashii Date: Sat, 3 Apr 2021 13:50:52 +0800 Subject: [PATCH 026/171] Fixed migration bugs --- packages/framework/lib/src/core/driver.dart | 82 ++++---- packages/framework/lib/src/core/routable.dart | 16 +- .../framework/lib/src/http/angel_http.dart | 29 ++- packages/framework/test/controller_test.dart | 21 +- packages/framework/test/di_test.dart | 34 ++-- packages/framework/test/general_test.dart | 13 +- packages/framework/test/hooked_test.dart | 53 +++-- packages/framework/test/routing_test.dart | 87 ++++----- packages/framework/test/serialize_test.dart | 15 +- packages/framework/test/services_test.dart | 39 ++-- packages/route/example/main.dart | 4 +- packages/route/lib/browser.dart | 116 ++++++----- packages/route/lib/src/grammar.dart | 69 +++++-- packages/route/lib/src/route.dart | 45 +++-- packages/route/lib/src/router.dart | 181 ++++++++++-------- packages/route/lib/src/routing_result.dart | 42 ++-- packages/route/lib/src/symlink_route.dart | 2 +- packages/route/lib/string_util.dart | 4 +- packages/route/test/chain_nest_test.dart | 2 +- packages/route/test/navigate_test.dart | 2 +- packages/route/test/wildcard_test.dart | 6 +- packages/route/web/shared/basic.dart | 27 +-- 22 files changed, 477 insertions(+), 412 deletions(-) diff --git a/packages/framework/lib/src/core/driver.dart b/packages/framework/lib/src/core/driver.dart index f1cf5773..a38f5d7c 100644 --- a/packages/framework/lib/src/core/driver.dart +++ b/packages/framework/lib/src/core/driver.dart @@ -4,6 +4,7 @@ import 'dart:io' show stderr, Cookie; import 'package:angel_http_exception/angel_http_exception.dart'; import 'package:angel_route/angel_route.dart'; import 'package:combinator/combinator.dart'; +import 'package:logging/logging.dart'; import 'package:stack_trace/stack_trace.dart'; import 'package:tuple/tuple.dart'; import 'core.dart'; @@ -17,11 +18,12 @@ abstract class Driver< Server extends Stream, RequestContextType extends RequestContext, ResponseContextType extends ResponseContext> { - final Angel? app; + final Angel app; final bool useZone; bool _closed = false; - Server? _server; + late Server _server; StreamSubscription? _sub; + final log = Logger('Driver'); /// The function used to bind this instance to a server.. final Future Function(dynamic, int) serverGenerator; @@ -32,39 +34,44 @@ abstract class Driver< Uri get uri; /// The native server running this instance. - Server? get server => _server; + Server get server => _server; Future generateServer(address, int port) => serverGenerator(address, port); /// Starts, and returns the server. - Future startServer([address, int? port]) { + Future startServer([address, int port = 5000]) { var host = address ?? '127.0.0.1'; - return generateServer(host, port ?? 0).then((server) { + return generateServer(host, port).then((server) { _server = server; - return Future.wait(app!.startupHooks.map(app!.configure)).then((_) { - app!.optimizeForProduction(); + return Future.wait(app.startupHooks.map(app.configure)).then((_) { + app.optimizeForProduction(); _sub = server.listen((request) { var stream = createResponseStreamFromRawRequest(request); stream.listen((response) { // TODO: To be revisited handleRawRequest(request, response); - return; }); }); - return _server!; + return Future.value(_server); }); + }).catchError((error) { + log.severe("Failed to create server", error); + throw ArgumentError("[Driver]Failed to create server"); }); } /// Shuts down the underlying server. Future close() { - if (_closed) return Future.value(_server); + if (_closed) { + return Future.value(_server); + } _closed = true; + _sub?.cancel(); - return app!.close().then((_) => - Future.wait(app!.shutdownHooks.map(app!.configure)) - .then((_) => _server!)); + return app.close().then((_) => + Future.wait(app.shutdownHooks.map(app.configure)) + .then((_) => Future.value(_server))); } Future createRequestContext( @@ -102,7 +109,7 @@ abstract class Driver< Tuple4, ParseResult?, MiddlewarePipeline> resolveTuple() { - var r = app!.optimizedRouter; + var r = app.optimizedRouter; var resolved = r.resolveAbsolute(path, method: req.method!, strip: false); var pipeline = MiddlewarePipeline(resolved); @@ -116,8 +123,8 @@ abstract class Driver< } var cacheKey = req.method! + path!; - var tuple = app!.environment.isProduction - ? app!.handlerCache.putIfAbsent(cacheKey, resolveTuple) + var tuple = app.environment.isProduction + ? app.handlerCache.putIfAbsent(cacheKey, resolveTuple) : resolveTuple(); var line = tuple.item4 as MiddlewarePipeline; var it = MiddlewarePipelineIterator(line); @@ -134,7 +141,7 @@ abstract class Driver< ..registerSingleton?>(tuple.item3) ..registerSingleton(tuple.item3); - if (!app!.environment.isProduction && app!.logger != null) { + if (app.environment.isProduction && app.logger != null) { req.container!.registerSingleton(Stopwatch()..start()); } @@ -160,14 +167,14 @@ abstract class Driver< stackTrace: st, statusCode: 500, message: e?.toString() ?? '500 Internal Server Error'); - }, test: (e) => e is! AngelHttpException).catchError( + }, test: (e) => e is AngelHttpException).catchError( (ee, StackTrace st) { var e = ee as AngelHttpException; - if (app!.logger != null) { + if (app.logger != null) { var error = e.error ?? e; var trace = Trace.from(e.stackTrace ?? StackTrace.current).terse; - app!.logger!.severe(e.message ?? e.toString(), error, trace); + app.logger?.severe(e.message ?? e.toString(), error, trace); } return handleAngelHttpException( @@ -176,8 +183,8 @@ abstract class Driver< } else { var zoneSpec = ZoneSpecification( print: (self, parent, zone, line) { - if (app!.logger != null) { - app!.logger!.info(line); + if (app.logger != null) { + app.logger?.info(line); } else { parent.print(zone, line); } @@ -198,8 +205,8 @@ abstract class Driver< stackTrace: stackTrace, message: error.toString()); } - if (app!.logger != null) { - app!.logger!.severe(e.message ?? e.toString(), error, trace); + if (app.logger != null) { + app.logger?.severe(e.message ?? e.toString(), error, trace); } return handleAngelHttpException( @@ -209,8 +216,8 @@ abstract class Driver< closeResponse(response); // Ideally, we won't be in a position where an absolutely fatal error occurs, // but if so, we'll need to log it. - if (app!.logger != null) { - app!.logger!.severe( + if (app.logger != null) { + app.logger?.severe( 'Fatal error occurred when processing $uri.', e, trace); } else { stderr @@ -220,7 +227,6 @@ abstract class Driver< ..writeln(trace); } }); - return; }, ); @@ -243,7 +249,7 @@ abstract class Driver< } /// Handles an [AngelHttpException]. - Future? handleAngelHttpException( + Future handleAngelHttpException( AngelHttpException e, StackTrace st, RequestContext? req, @@ -253,12 +259,12 @@ abstract class Driver< {bool ignoreFinalizers = false}) { if (req == null || res == null) { try { - app!.logger?.severe(null, e, st); + app.logger?.severe(null, e, st); setStatusCode(response, 500); writeStringToResponse(response, '500 Internal Server Error'); closeResponse(response); } finally { - return null; + return Future.value(); } } @@ -269,8 +275,8 @@ abstract class Driver< } else { res.statusCode = e.statusCode; handleError = - Future.sync(() => app!.errorHandler(e, req, res)).then((result) { - return app!.executeHandler(result, req, res).then((_) => res.close()); + Future.sync(() => app.errorHandler(e, req, res)).then((result) { + return app.executeHandler(result, req, res).then((_) => res.close()); }); } @@ -283,11 +289,11 @@ abstract class Driver< ResponseContext res, {bool ignoreFinalizers = false}) { Future _cleanup(_) { - if (!app!.environment.isProduction && - app!.logger != null && + if (app.environment.isProduction && + app.logger != null && req.container!.has()) { var sw = req.container!.make(); - app!.logger!.info( + app.logger?.info( "${res.statusCode} ${req.method} ${req.uri} (${sw?.elapsedMilliseconds ?? 'unknown'} ms)"); } return req.close(); @@ -297,7 +303,7 @@ abstract class Driver< Future finalizers = ignoreFinalizers == true ? Future.value() - : Future.forEach(app!.responseFinalizers, (dynamic f) => f(req, res)); + : Future.forEach(app.responseFinalizers, (dynamic f) => f(req, res)); return finalizers.then((_) { //if (res.isOpen) res.close(); @@ -358,13 +364,13 @@ abstract class Driver< MiddlewarePipelineIterator it, RequestContextType req, ResponseContextType res, - Angel? app) async { + Angel app) async { var broken = false; while (it.moveNext()) { var current = it.current.handlers.iterator; while (!broken && current.moveNext()) { - var result = await app!.executeHandler(current.current, req, res); + var result = await app.executeHandler(current.current, req, res); if (result != true) { broken = true; break; diff --git a/packages/framework/lib/src/core/routable.dart b/packages/framework/lib/src/core/routable.dart index b63a27a5..a52667ed 100644 --- a/packages/framework/lib/src/core/routable.dart +++ b/packages/framework/lib/src/core/routable.dart @@ -30,7 +30,7 @@ RequestHandler chain(Iterable handlers) { runPipeline = () => Future.sync(() => handler(req, res)); } else { var current = runPipeline; - runPipeline = () => current().then((result) => !res.isOpen + runPipeline = () => current().then((result) => res.isOpen ? Future.value(result) : req.app.executeHandler(handler, req, res)); } @@ -42,7 +42,7 @@ RequestHandler chain(Iterable handlers) { } /// A routable server that can handle dynamic requests. -class Routable extends Router { +class Routable extends Router { final Map _services = {}; final Map _serviceLookups = {}; final Map configuration = {}; @@ -92,10 +92,9 @@ class Routable extends Router { } @override - Route addRoute( - String? method, String? path, RequestHandler? handler, - {Iterable? middleware}) { - middleware ??= []; + Route addRoute( + String method, String path, RequestHandler handler, + {Iterable middleware = const Iterable.empty()}) { final handlers = []; // Merge @Middleware declaration, if any var reflector = _container?.reflector; @@ -108,10 +107,7 @@ class Routable extends Router { } final handlerSequence = []; - handlerSequence.addAll(middleware as Iterable< - FutureOr? Function( - RequestContext, ResponseContext)>? ?? - []); + handlerSequence.addAll(middleware); handlerSequence.addAll(handlers); return super.addRoute(method, path.toString(), handler, diff --git a/packages/framework/lib/src/http/angel_http.dart b/packages/framework/lib/src/http/angel_http.dart index 0276f190..dcb8ef34 100644 --- a/packages/framework/lib/src/http/angel_http.dart +++ b/packages/framework/lib/src/http/angel_http.dart @@ -19,15 +19,19 @@ final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)'); class AngelHttp extends Driver { @override - Uri get uri => server == null - ? Uri() - : Uri(scheme: 'http', host: server!.address.address, port: server!.port); + Uri get uri { + if (server == null) { + throw ArgumentError("[AngelHttp] Server instance not intialised"); + } + return Uri( + scheme: 'http', host: server!.address.address, port: server!.port); + } - AngelHttp._(Angel? app, + AngelHttp._(Angel app, Future Function(dynamic, int) serverGenerator, bool useZone) : super(app, serverGenerator, useZone: useZone); - factory AngelHttp(Angel? app, {bool useZone = true}) { + factory AngelHttp(Angel app, {bool useZone = true}) { return AngelHttp._(app, HttpServer.bind, useZone); } @@ -59,12 +63,18 @@ class AngelHttp extends Driver server; + HttpServer get httpServer { + if (server == null) { + throw ArgumentError("[AngelHttp] Server instance not initialised"); + } + return server!; + } Future handleRequest(HttpRequest request) => handleRawRequest(request, request.response); @@ -75,7 +85,6 @@ class AngelHttp extends Driver close() async { - await server?.close(); return await super.close(); } @@ -87,7 +96,7 @@ class AngelHttp extends Driver.value(context); // return Future.value( diff --git a/packages/framework/test/controller_test.dart b/packages/framework/test/controller_test.dart index a75b998f..23402a7b 100644 --- a/packages/framework/test/controller_test.dart +++ b/packages/framework/test/controller_test.dart @@ -70,7 +70,7 @@ bool bar(RequestContext req, ResponseContext res) { } main() { - Angel? app; + late Angel app; late TodoController todoController; late NoExposeController noExposeCtrl; late HttpServer server; @@ -79,30 +79,30 @@ main() { setUp(() async { app = Angel(reflector: MirrorsReflector()); - app!.get( + app.get( "/redirect", (req, res) async => res.redirectToAction("TodoController@foo", {"foo": "world"})); // Register as a singleton, just for the purpose of this test - if (!app!.container!.has()) { - app!.container!.registerSingleton(todoController = TodoController()); + if (!app.container!.has()) { + app.container!.registerSingleton(todoController = TodoController()); } // Using mountController(); - await app!.mountController(); + await app.mountController(); - noExposeCtrl = await app!.mountController(); + noExposeCtrl = await app.mountController(); // Place controller in group. The applyRoutes() call, however, is async. // Until https://github.com/angel-dart/route/issues/28 is closed, // this will need to be done by manually mounting the router. var subRouter = Router(); - await todoController.applyRoutes(subRouter, app!.container!.reflector); - app!.mount('/ctrl_group', subRouter); + await todoController.applyRoutes(subRouter, app.container!.reflector); + app.mount('/ctrl_group', subRouter); - print(app!.controllers); - app!.dumpTree(); + print(app.controllers); + app.dumpTree(); server = await AngelHttp(app).startServer(); url = 'http://${server.address.address}:${server.port}'; @@ -110,7 +110,6 @@ main() { tearDown(() async { await server.close(force: true); - app = null; url = null; }); diff --git a/packages/framework/test/di_test.dart b/packages/framework/test/di_test.dart index 51e9999e..c8c12e44 100644 --- a/packages/framework/test/di_test.dart +++ b/packages/framework/test/di_test.dart @@ -15,8 +15,8 @@ final String TEXT = "make your bed"; final String OVER = "never"; main() { - Angel? app; - http.Client? client; + late Angel app; + late http.Client client; late HttpServer server; String? url; @@ -25,31 +25,29 @@ main() { client = http.Client(); // Inject some todos - app!.container!.registerSingleton(Todo(text: TEXT, over: OVER)); - app!.container!.registerFactory>((container) async { + app.container!.registerSingleton(Todo(text: TEXT, over: OVER)); + app.container!.registerFactory>((container) async { var req = container.make()!; var text = await utf8.decoder.bind(req.body!).join(); return Foo(text); }); - app!.get("/errands", ioc((Todo singleton) => singleton)); - app!.get( + app.get("/errands", ioc((Todo singleton) => singleton)); + app.get( "/errands3", ioc(({required Errand singleton, Todo? foo, RequestContext? req}) => singleton.text)); - app!.post('/async', ioc((Foo foo) => {'baz': foo.bar})); - await app!.configure(SingletonController().configureServer); - await app!.configure(ErrandController().configureServer); + app.post('/async', ioc((Foo foo) => {'baz': foo.bar})); + await app.configure(SingletonController().configureServer); + await app.configure(ErrandController().configureServer); server = await AngelHttp(app).startServer(); url = "http://${server.address.host}:${server.port}"; }); tearDown(() async { - app = null; url = null; - client!.close(); - client = null; + client.close(); await server.close(force: true); }); @@ -71,33 +69,33 @@ main() { }); test("singleton in route", () async { - validateTodoSingleton(await client!.get(Uri.parse("$url/errands"))); + validateTodoSingleton(await client.get(Uri.parse("$url/errands"))); }); test("singleton in controller", () async { - validateTodoSingleton(await client!.get(Uri.parse("$url/errands2"))); + validateTodoSingleton(await client.get(Uri.parse("$url/errands2"))); }); test("make in route", () async { - var response = await client!.get(Uri.parse("$url/errands3")); + var response = await client.get(Uri.parse("$url/errands3")); var text = await json.decode(response.body) as String?; expect(text, equals(TEXT)); }); test("make in controller", () async { - var response = await client!.get(Uri.parse("$url/errands4")); + var response = await client.get(Uri.parse("$url/errands4")); var text = await json.decode(response.body) as String?; expect(text, equals(TEXT)); }); test('resolve from future in controller', () async { var response = - await client!.post(Uri.parse('$url/errands4/async'), body: 'hey'); + await client.post(Uri.parse('$url/errands4/async'), body: 'hey'); expect(response.body, json.encode({'bar': 'hey'})); }); test('resolve from future in route', () async { - var response = await client!.post(Uri.parse('$url/async'), body: 'yes'); + var response = await client.post(Uri.parse('$url/async'), body: 'yes'); expect(response.body, json.encode({'baz': 'yes'})); }); } diff --git a/packages/framework/test/general_test.dart b/packages/framework/test/general_test.dart index b279c6c3..b21c02c7 100644 --- a/packages/framework/test/general_test.dart +++ b/packages/framework/test/general_test.dart @@ -7,10 +7,10 @@ import 'package:http/http.dart' as http; import 'package:test/test.dart'; main() { - Angel? app; - http.Client? client; + late Angel app; + late http.Client client; late HttpServer server; - String? url; + late String url; setUp(() async { app = Angel(reflector: MirrorsReflector()) @@ -23,15 +23,12 @@ main() { }); tearDown(() async { - app = null; - url = null; - client!.close(); - client = null; + client.close(); await server.close(force: true); }); test("allow override of method", () async { - var response = await client!.get(Uri.parse('$url/foo'), + var response = await client.get(Uri.parse('$url/foo'), headers: {'X-HTTP-Method-Override': 'POST'}); print('Response: ${response.body}'); expect(json.decode(response.body), equals({'hello': 'world'})); diff --git a/packages/framework/test/hooked_test.dart b/packages/framework/test/hooked_test.dart index 7b01ac05..635fb9cc 100644 --- a/packages/framework/test/hooked_test.dart +++ b/packages/framework/test/hooked_test.dart @@ -13,25 +13,26 @@ main() { 'Content-Type': 'application/json' }; - Angel? app; + late Angel app; late HttpServer server; - String? url; - http.Client? client; - HookedService? todoService; + late String url; + late http.Client client; + late HookedService todoService; setUp(() async { app = Angel(reflector: MirrorsReflector()); client = http.Client(); - app!.use('/todos', MapService()); - app!.use('/books', BookService()); + app.use('/todos', MapService()); + app.use('/books', BookService()); - todoService = app!.findHookedService('todos'); + todoService = app.findHookedService('todos') + as HookedService; - todoService!.beforeAllStream().listen((e) { + todoService.beforeAllStream().listen((e) { print('Fired ${e.eventName}! Data: ${e.data}; Params: ${e.params}'); }); - app!.errorHandler = (e, req, res) { + app.errorHandler = (e, req, res) { throw e.error as Object; }; @@ -41,31 +42,27 @@ main() { tearDown(() async { await server.close(force: true); - app = null; - url = null; - client!.close(); - client = null; - todoService = null; + client.close(); }); test("listen before and after", () async { int count = 0; todoService - ?..beforeIndexed.listen((_) { + ..beforeIndexed.listen((_) { count++; }) ..afterIndexed.listen((_) { count++; }); - var response = await client!.get(Uri.parse("$url/todos")); + var response = await client.get(Uri.parse("$url/todos")); print(response.body); expect(count, equals(2)); }); test("cancel before", () async { - todoService!.beforeCreated + todoService.beforeCreated ..listen((HookedServiceEvent event) { event.cancel({"hello": "hooked world"}); }) @@ -73,7 +70,7 @@ main() { event.cancel({"this_hook": "should never run"}); }); - var response = await client!.post(Uri.parse("$url/todos"), + var response = await client.post(Uri.parse("$url/todos"), body: json.encode({"arbitrary": "data"}), headers: headers as Map); print(response.body); @@ -82,7 +79,7 @@ main() { }); test("cancel after", () async { - todoService!.afterIndexed + todoService.afterIndexed ..listen((HookedServiceEvent event) async { // Hooks can be Futures ;) event.cancel([ @@ -93,20 +90,20 @@ main() { event.cancel({"this_hook": "should never run either"}); }); - var response = await client!.get(Uri.parse("$url/todos")); + var response = await client.get(Uri.parse("$url/todos")); print(response.body); var result = json.decode(response.body) as List; expect(result[0]["angel"], equals("framework")); }); test('asStream() fires', () async { - var stream = todoService!.afterCreated.asStream(); - await todoService!.create({'angel': 'framework'}); + var stream = todoService.afterCreated.asStream(); + await todoService.create({'angel': 'framework'}); expect(await stream.first.then((e) => e.result['angel']), 'framework'); }); test('metadata', () async { - final service = HookedService(IncrementService())..addHooks(app!); + final service = HookedService(IncrementService())..addHooks(app); expect(service.inner, isNot(const IsInstanceOf())); IncrementService.TIMES = 0; await service.index(); @@ -114,15 +111,15 @@ main() { }); test('inject request + response', () async { - HookedService books = app!.findService('books') + HookedService books = app.findService('books') as HookedService>; books.beforeIndexed.listen((e) { expect([e.request, e.response], everyElement(isNotNull)); - print('Indexing books at path: ${e.request!.path}'); + print('Indexing books at path: ${e.request?.path}'); }); - var response = await client!.get(Uri.parse('$url/books')); + var response = await client.get(Uri.parse('$url/books')); print(response.body); var result = json.decode(response.body); @@ -138,8 +135,8 @@ main() { var type = e.isBefore ? 'before' : 'after'; print('Params to $type ${e.eventName}: ${e.params}'); expect(e.params, isMap); - expect(e.params!.keys, contains('provider')); - expect(e.params!['provider'], const IsInstanceOf()); + expect(e.params?.keys, contains('provider')); + expect(e.params?['provider'], const IsInstanceOf()); } svc diff --git a/packages/framework/test/routing_test.dart b/packages/framework/test/routing_test.dart index 97469472..36426968 100644 --- a/packages/framework/test/routing_test.dart +++ b/packages/framework/test/routing_test.dart @@ -35,11 +35,11 @@ bool interceptService(RequestContext req, ResponseContext res) { } main() { - Angel? app; - Angel? nested; - Angel? todos; - String? url; - http.Client? client; + late Angel app; + late Angel nested; + late Angel todos; + late String url; + late http.Client client; setUp(() async { app = Angel(reflector: MirrorsReflector()); @@ -47,7 +47,7 @@ main() { todos = Angel(reflector: MirrorsReflector()); [app, nested, todos].forEach((Angel? app) { - app!.logger = Logger('routing_test') + app?.logger = Logger('routing_test') ..onRecord.listen((rec) { if (rec.error != null) { stdout @@ -58,44 +58,44 @@ main() { }); }); - todos!.get('/action/:action', (req, res) => res.json(req.params)); + todos.get('/action/:action', (req, res) => res.json(req.params)); late Route ted; - ted = nested!.post('/ted/:route', (RequestContext req, res) { + ted = nested.post('/ted/:route', (RequestContext req, res) { print('Params: ${req.params}'); print('Path: ${ted.path}, uri: ${req.path}'); print('matcher: ${ted.parser}'); return req.params; }); - app!.mount('/nes', nested!); - app!.get('/meta', testMiddlewareMetadata); - app!.get('/intercepted', (req, res) => 'This should not be shown', + app.mount('/nes', nested); + app.get('/meta', testMiddlewareMetadata); + app.get('/intercepted', (req, res) => 'This should not be shown', middleware: [interceptor]); - app!.get('/hello', (req, res) => 'world'); - app!.get('/name/:first/last/:last', (req, res) => req.params); - app!.post( + app.get('/hello', (req, res) => 'world'); + app.get('/name/:first/last/:last', (req, res) => req.params); + app.post( '/lambda', (RequestContext req, res) => req.parseBody().then((_) => req.bodyAsMap)); - app!.mount('/todos/:id', todos!); - app! + app.mount('/todos/:id', todos); + app .get('/greet/:name', (RequestContext req, res) async => "Hello ${req.params['name']}") .name = 'Named routes'; - app!.get('/named', (req, ResponseContext res) async { + app.get('/named', (req, ResponseContext res) async { await res.redirectTo('Named routes', {'name': 'tests'}); }); - app!.get('/log', (RequestContext req, res) async { + app.get('/log', (RequestContext req, res) async { print("Query: ${req.queryParameters}"); return "Logged"; }); - app!.get('/method', (req, res) => 'Only GET'); - app!.post('/method', (req, res) => 'Only POST'); + app.get('/method', (req, res) => 'Only GET'); + app.post('/method', (req, res) => 'Only POST'); - app!.use('/query', QueryService()); + app.use('/query', QueryService()); RequestHandler write(String message) { return (req, res) { @@ -104,10 +104,10 @@ main() { }; } - app!.chain([write('a')]).chain([write('b'), write('c')]).get( + app.chain([write('a')]).chain([write('b'), write('c')]).get( '/chained', (req, res) => res.close()); - app!.fallback((req, res) => 'MJ'); + app.fallback((req, res) => 'MJ'); //app.dumpTree(header: "DUMPING ROUTES:", showMatchers: true); @@ -117,22 +117,17 @@ main() { }); tearDown(() async { - await app!.close(); - app = null; - nested = null; - todos = null; - client!.close(); - client = null; - url = null; + await app.close(); + client.close(); }); test('Can match basic url', () async { - var response = await client!.get(Uri.parse("$url/hello")); + var response = await client.get(Uri.parse("$url/hello")); expect(response.body, equals('"world"')); }); test('Can match url with multiple parameters', () async { - var response = await client!.get(Uri.parse('$url/name/HELLO/last/WORLD')); + var response = await client.get(Uri.parse('$url/name/HELLO/last/WORLD')); print('Response: ${response.body}'); var json_ = json.decode(response.body); expect(json_, const IsInstanceOf()); @@ -141,18 +136,18 @@ main() { }); test('Chained routes', () async { - var response = await client!.get(Uri.parse("$url/chained")); + var response = await client.get(Uri.parse("$url/chained")); expect(response.body, equals('abc')); }); test('Can nest another Angel instance', () async { - var response = await client!.post(Uri.parse('$url/nes/ted/foo')); + var response = await client.post(Uri.parse('$url/nes/ted/foo')); var json_ = json.decode(response.body); expect(json_['route'], equals('foo')); }); test('Can parse parameters from a nested Angel instance', () async { - var response = await client!.get(Uri.parse('$url/todos/1337/action/test')); + var response = await client.get(Uri.parse('$url/todos/1337/action/test')); var json_ = json.decode(response.body); print('JSON: $json_'); expect(json_['id'], equals('1337')); @@ -160,32 +155,32 @@ main() { }); test('Can add and use named middleware', () async { - var response = await client!.get(Uri.parse('$url/intercepted')); + var response = await client.get(Uri.parse('$url/intercepted')); expect(response.body, equals('Middleware')); }); test('Middleware via metadata', () async { // Metadata - var response = await client!.get(Uri.parse('$url/meta')); + var response = await client.get(Uri.parse('$url/meta')); expect(response.body, equals('Middleware')); }); test('Can serialize function result as JSON', () async { Map headers = {'Content-Type': 'application/json'}; String postData = json.encode({'it': 'works'}); - var response = await client!.post(Uri.parse("$url/lambda"), + var response = await client.post(Uri.parse("$url/lambda"), headers: headers as Map, body: postData); print('Response: ${response.body}'); expect(json.decode(response.body)['it'], equals('works')); }); test('Fallback routes', () async { - var response = await client!.get(Uri.parse('$url/my_favorite_artist')); + var response = await client.get(Uri.parse('$url/my_favorite_artist')); expect(response.body, equals('"MJ"')); }); test('Can name routes', () { - Route foo = app!.get('/framework/:id', null)..name = 'frm'; + Route foo = app.get('/framework/:id', null)..name = 'frm'; print('Foo: $foo'); String uri = foo.makeUri({'id': 'angel'}); print(uri); @@ -193,32 +188,32 @@ main() { }); test('Redirect to named routes', () async { - var response = await client!.get(Uri.parse('$url/named')); + var response = await client.get(Uri.parse('$url/named')); print(response.body); expect(json.decode(response.body), equals('Hello tests')); }); test('Match routes, even with query params', () async { - var response = await client! + var response = await client .get(Uri.parse("$url/log?foo=bar&bar=baz&baz.foo=bar&baz.bar=foo")); print(response.body); expect(json.decode(response.body), equals('Logged')); - response = await client!.get(Uri.parse("$url/query/foo?bar=baz")); + response = await client.get(Uri.parse("$url/query/foo?bar=baz")); print(response.body); expect(response.body, equals("Service with Middleware")); }); test('only match route with matching method', () async { - var response = await client!.get(Uri.parse("$url/method")); + var response = await client.get(Uri.parse("$url/method")); print(response.body); expect(response.body, '"Only GET"'); - response = await client!.post(Uri.parse("$url/method")); + response = await client.post(Uri.parse("$url/method")); print(response.body); expect(response.body, '"Only POST"'); - response = await client!.patch(Uri.parse("$url/method")); + response = await client.patch(Uri.parse("$url/method")); print(response.body); expect(response.body, '"MJ"'); }); diff --git a/packages/framework/test/serialize_test.dart b/packages/framework/test/serialize_test.dart index d9d20ce4..d15ea31c 100644 --- a/packages/framework/test/serialize_test.dart +++ b/packages/framework/test/serialize_test.dart @@ -8,10 +8,10 @@ import 'package:http_parser/http_parser.dart'; import 'package:test/test.dart'; main() { - Angel? app; - http.Client? client; + late Angel app; + late http.Client client; late HttpServer server; - String? url; + late String url; setUp(() async { app = Angel(reflector: MirrorsReflector()) @@ -27,19 +27,16 @@ main() { }); tearDown(() async { - app = null; - url = null; - client!.close(); - client = null; + client.close(); await server.close(force: true); }); test("correct content-type", () async { - var response = await client!.get(Uri.parse('$url/foo')); + var response = await client.get(Uri.parse('$url/foo')); print('Response: ${response.body}'); expect(response.headers['content-type'], contains('application/json')); - response = await client!.get(Uri.parse('$url/bar')); + response = await client.get(Uri.parse('$url/bar')); print('Response: ${response.body}'); expect(response.headers['content-type'], contains('text/html')); }); diff --git a/packages/framework/test/services_test.dart b/packages/framework/test/services_test.dart index f5a0717e..05bd8c51 100644 --- a/packages/framework/test/services_test.dart +++ b/packages/framework/test/services_test.dart @@ -18,10 +18,10 @@ main() { 'Accept': 'application/json', 'Content-Type': 'application/json' }; - Angel? app; + late Angel app; late MapService service; - String? url; - http.Client? client; + late String url; + late http.Client client; setUp(() async { app = Angel(reflector: MirrorsReflector()) @@ -37,16 +37,13 @@ main() { }); tearDown(() async { - await app!.close(); - app = null; - url = null; - client!.close(); - client = null; + await app.close(); + client.close(); }); group('memory', () { test('can index an empty service', () async { - var response = await client!.get(Uri.parse("$url/todos/")); + var response = await client.get(Uri.parse("$url/todos/")); print(response.body); expect(response.body, equals('[]')); print(response.body); @@ -55,7 +52,7 @@ main() { test('can create data', () async { String postData = json.encode({'text': 'Hello, world!'}); - var response = await client!.post(Uri.parse("$url/todos"), + var response = await client.post(Uri.parse("$url/todos"), headers: headers as Map, body: postData); expect(response.statusCode, 201); var jsons = json.decode(response.body); @@ -65,9 +62,9 @@ main() { test('can fetch data', () async { String postData = json.encode({'text': 'Hello, world!'}); - await client!.post(Uri.parse("$url/todos"), + await client.post(Uri.parse("$url/todos"), headers: headers as Map, body: postData); - var response = await client!.get(Uri.parse("$url/todos/0")); + var response = await client.get(Uri.parse("$url/todos/0")); expect(response.statusCode, 200); var jsons = json.decode(response.body); print(jsons); @@ -76,12 +73,12 @@ main() { test('can modify data', () async { String postData = json.encode({'text': 'Hello, world!'}); - await client!.post(Uri.parse("$url/todos"), + await client.post(Uri.parse("$url/todos"), headers: headers as Map, body: postData); postData = json.encode({'text': 'modified'}); - var response = await client! - .patch(Uri.parse("$url/todos/0"), headers: headers, body: postData); + var response = await client.patch(Uri.parse("$url/todos/0"), + headers: headers, body: postData); expect(response.statusCode, 200); var jsons = json.decode(response.body); print(jsons); @@ -90,12 +87,12 @@ main() { test('can overwrite data', () async { String postData = json.encode({'text': 'Hello, world!'}); - await client!.post(Uri.parse("$url/todos"), + await client.post(Uri.parse("$url/todos"), headers: headers as Map, body: postData); postData = json.encode({'over': 'write'}); - var response = await client! - .post(Uri.parse("$url/todos/0"), headers: headers, body: postData); + var response = await client.post(Uri.parse("$url/todos/0"), + headers: headers, body: postData); expect(response.statusCode, 200); var jsons = json.decode(response.body); print(jsons); @@ -116,12 +113,12 @@ main() { test('can delete data', () async { String postData = json.encode({'text': 'Hello, world!'}); - var created = await client! + var created = await client .post(Uri.parse("$url/todos"), headers: headers as Map, body: postData) .then((r) => json.decode(r.body)); var response = - await client!.delete(Uri.parse("$url/todos/${created['id']}")); + await client.delete(Uri.parse("$url/todos/${created['id']}")); expect(response.statusCode, 200); var json_ = json.decode(response.body); print(json_); @@ -129,7 +126,7 @@ main() { }); test('cannot remove all unless explicitly set', () async { - var response = await client!.delete(Uri.parse('$url/todos/null')); + var response = await client.delete(Uri.parse('$url/todos/null')); expect(response.statusCode, 403); }); }); diff --git a/packages/route/example/main.dart b/packages/route/example/main.dart index b0352ac2..891dd750 100644 --- a/packages/route/example/main.dart +++ b/packages/route/example/main.dart @@ -2,7 +2,7 @@ import 'dart:math'; import 'package:angel_route/angel_route.dart'; -main() { +void main() { final router = Router(); router.get('/whois/~:user', () {}); @@ -12,7 +12,7 @@ main() { router.get('/ordinal/int:n([0-9]+)st', () {}); print(router.resolveAbsolute('/whois/~thosakwe').first.allParams); - print(router.resolveAbsolute('/wild_thornberrys').first.route!.path); + print(router.resolveAbsolute('/wild_thornberrys').first.route.path); print(router.resolveAbsolute('/ordinal/1st').first.allParams); router.get('/users', () {}); diff --git a/packages/route/lib/browser.dart b/packages/route/lib/browser.dart index eb54fe45..dae09d2f 100644 --- a/packages/route/lib/browser.dart +++ b/packages/route/lib/browser.dart @@ -11,10 +11,10 @@ final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)'); /// A variation of the [Router] support both hash routing and push state. abstract class BrowserRouter extends Router { /// Fires whenever the active route changes. Fires `null` if none is selected (404). - Stream?> get onResolve; + Stream> get onResolve; /// Fires whenever the active route changes. Fires `null` if none is selected (404). - Stream?> get onRoute; + Stream> get onRoute; /// Set `hash` to true to use hash routing instead of push state. /// `listen` as `true` will call `listen` after initialization. @@ -26,7 +26,7 @@ abstract class BrowserRouter extends Router { BrowserRouter._() : super(); - void _goTo(String? path); + void _goTo(String path); /// Navigates to the path generated by calling /// [navigate] with the given [linkParams]. @@ -41,26 +41,26 @@ abstract class BrowserRouter extends Router { void listen(); /// Identical to [all]. - Route on(String path, T handler, {Iterable? middleware}); + Route on(String path, T handler, {Iterable middleware}); } abstract class _BrowserRouterImpl extends Router implements BrowserRouter { bool _listening = false; Route? _current; - StreamController?> _onResolve = - StreamController?>(); - StreamController?> _onRoute = StreamController?>(); + final StreamController> _onResolve = + StreamController>(); + final StreamController> _onRoute = StreamController>(); Route? get currentRoute => _current; @override - Stream?> get onResolve => _onResolve.stream; + Stream> get onResolve => _onResolve.stream; @override - Stream?> get onRoute => _onRoute.stream; + Stream> get onRoute => _onRoute.stream; - _BrowserRouterImpl({bool? listen}) : super() { + _BrowserRouterImpl({bool listen = false}) : super() { if (listen != false) this.listen(); prepareAnchors(); } @@ -68,7 +68,9 @@ abstract class _BrowserRouterImpl extends Router @override void go(Iterable linkParams) => _goTo(navigate(linkParams)); - Route on(String path, T handler, {Iterable? middleware}) => + @override + Route on(String path, T handler, + {Iterable middleware = const Iterable.empty()}) => all(path, handler, middleware: middleware); void prepareAnchors() { @@ -76,14 +78,14 @@ abstract class _BrowserRouterImpl extends Router .querySelectorAll('a') .cast(); //:not([dynamic])'); - for (final AnchorElement $a in anchors) { + for (final $a in anchors) { if ($a.attributes.containsKey('href') && - !$a.attributes.containsKey('download') && - !$a.attributes.containsKey('target') && + $a.attributes.containsKey('download') && + $a.attributes.containsKey('target') && $a.attributes['rel'] != 'external') { $a.onClick.listen((e) { e.preventDefault(); - _goTo($a.attributes['href']); + _goTo($a.attributes['href']!); //go($a.attributes['href'].split('/').where((str) => str.isNotEmpty)); }); } @@ -110,32 +112,36 @@ class _HashRouter extends _BrowserRouterImpl { } @override - void _goTo(String? uri) { + void _goTo(String uri) { window.location.hash = '#$uri'; } void handleHash([_]) { final path = window.location.hash.replaceAll(_hash, ''); - Iterable> allResolved = resolveAbsolute(path); + var allResolved = resolveAbsolute(path); - final resolved = allResolved.isEmpty ? null : allResolved.first; - - if (resolved == null) { - _onResolve.add(null); - _onRoute.add(_current = null); - } else if (resolved != null && resolved.route != _current) { - _onResolve.add(resolved); - _onRoute.add(_current = resolved.route); + if (allResolved.isEmpty) { + // TODO: Need fixing + //_onResolve.add(null); + //_onRoute.add(_current = null); + _current = null; + } else { + var resolved = allResolved.first; + if (resolved.route != _current) { + _onResolve.add(resolved); + _onRoute.add(_current = resolved.route); + } } } void handlePath(String path) { - final RoutingResult resolved = resolveAbsolute(path).first; + final resolved = resolveAbsolute(path).first; - if (resolved == null) { - _onResolve.add(null); - _onRoute.add(_current = null); - } else if (resolved != null && resolved.route != _current) { + //if (resolved == null) { + // _onResolve.add(null); + // _onRoute.add(_current = null); + //} else + if (resolved.route != _current) { _onResolve.add(resolved); _onRoute.add(_current = resolved.route); } @@ -149,12 +155,12 @@ class _HashRouter extends _BrowserRouterImpl { } class _PushStateRouter extends _BrowserRouterImpl { - String? _basePath; + late String _basePath; - _PushStateRouter({required bool listen, Route? root}) : super(listen: listen) { + _PushStateRouter({required bool listen}) : super(listen: listen) { var $base = window.document.querySelector('base[href]') as BaseElement; - if ($base?.href?.isNotEmpty != true) { + if ($base.href.isNotEmpty != true) { throw StateError( 'You must have a element present in your document to run the push state router.'); } @@ -163,42 +169,48 @@ class _PushStateRouter extends _BrowserRouterImpl { } @override - void _goTo(String? uri) { - final RoutingResult resolved = resolveAbsolute(uri).first; + void _goTo(String uri) { + final resolved = resolveAbsolute(uri).first; var relativeUri = uri; - if (_basePath?.isNotEmpty == true) { - relativeUri = p.join(_basePath!, uri!.replaceAll(_straySlashes, '')); + if (_basePath.isNotEmpty) { + relativeUri = p.join(_basePath, uri.replaceAll(_straySlashes, '')); } - if (resolved == null) { - _onResolve.add(null); - _onRoute.add(_current = null); - } else { - final route = resolved.route!; - window.history.pushState({'path': route.path, 'params': {}}, - route.name ?? route.path, relativeUri); - _onResolve.add(resolved); - _onRoute.add(_current = route); + //if (resolved == null) { + // _onResolve.add(null); + // _onRoute.add(_current = null); + //} else { + final route = resolved.route; + var thisPath = route.name; + if (thisPath.isEmpty) { + thisPath = route.path; } + window.history + .pushState({'path': route.path, 'params': {}}, thisPath, relativeUri); + _onResolve.add(resolved); + _onRoute.add(_current = route); + //} } void handleState(state) { if (state is Map && state.containsKey('path')) { var path = state['path'].toString(); - final RoutingResult resolved = resolveAbsolute(path).first; + final resolved = resolveAbsolute(path).first; - if (resolved != null && resolved.route != _current) { + if (resolved.route != _current) { //properties.addAll(state['properties'] ?? {}); _onResolve.add(resolved); _onRoute.add(_current = resolved.route); } else { - _onResolve.add(null); - _onRoute.add(_current = null); + //_onResolve.add(null); + //_onRoute.add(_current = null); + _current = null; } } else { - _onResolve.add(null); - _onRoute.add(_current = null); + //_onResolve.add(null); + //_onRoute.add(_current = null); + _current = null; } } diff --git a/packages/route/lib/src/grammar.dart b/packages/route/lib/src/grammar.dart index 3f970ba7..9d7df15b 100644 --- a/packages/route/lib/src/grammar.dart +++ b/packages/route/lib/src/grammar.dart @@ -40,7 +40,9 @@ class RouteGrammar { } } - var s = ParameterSegment(match[2], rgx); + // TODO: relook at this later + var m2 = match[2] ?? ''; + var s = ParameterSegment(m2, rgx); return r.value![1] == true ? OptionalSegment(s) : s; }); @@ -95,12 +97,12 @@ class RouteDefinition { RouteDefinition(this.segments); - Parser? compile() { - Parser? out; + Parser? compile() { + Parser? out; - for (int i = 0; i < segments.length; i++) { + for (var i = 0; i < segments.length; i++) { var s = segments[i]; - bool isLast = i == segments.length - 1; + var isLast = i == segments.length - 1; if (out == null) { out = s.compile(isLast); } else { @@ -116,7 +118,7 @@ class RouteDefinition { abstract class RouteSegment { Parser compile(bool isLast); - Parser compileNext(Parser p, bool isLast); + Parser compileNext(Parser p, bool isLast); } class SlashSegment implements RouteSegment { @@ -182,8 +184,15 @@ class WildcardSegment extends RouteSegment { @override Parser compile(bool isLast) { - return match(_compile(isLast)) - .map((r) => RouteResult({}, tail: r.scanner.lastMatch![1])); + return match(_compile(isLast)).map((r) { + var result = r.scanner.lastMatch; + if (result != null) { + //return RouteResult({}, tail: r.scanner.lastMatch![1]) + return RouteResult({}, tail: result[1]); + } else { + return RouteResult({}); + } + }); } @override @@ -214,31 +223,53 @@ class OptionalSegment extends ParameterSegment { } @override - Parser compileNext(Parser p, bool isLast) { + Parser compileNext(Parser p, bool isLast) { return p.then(_compile().opt()).map((r) { - if (r.value![1] == null) return r.value![0] as RouteResult?; - return (r.value![0] as RouteResult) - ..addAll({name: Uri.decodeComponent(r.value![1] as String)}); + // Return an empty RouteResult if null + if (r.value == null) { + return RouteResult({}); + } + + var v = r.value!; + + if (v[1] == null) { + return v[0] as RouteResult; + } + return (v[0] as RouteResult) + ..addAll({name: Uri.decodeComponent(v as String)}); }); } } class ParameterSegment extends RouteSegment { - final String? name; + final String name; final RegExp? regExp; ParameterSegment(this.name, this.regExp); @override String toString() { - if (regExp != null) return 'Param: $name (${regExp!.pattern})'; + if (regExp != null) { + return 'Param: $name (${regExp?.pattern})'; + } return 'Param: $name'; } - Parser _compile() { - return regExp != null - ? match(regExp!).value((r) => r.scanner.lastMatch![1]) - : RouteGrammar.notSlash; + Parser _compile() { + if (regExp != null) { + return match(regExp!).value((r) { + var result = r.scanner.lastMatch; + if (result != null) { + // TODO: Invalid method + //return r.scanner.lastMatch![1]; + return result.toString(); + } else { + return ''; + } + }); + } else { + return RouteGrammar.notSlash; + } } @override @@ -248,7 +279,7 @@ class ParameterSegment extends RouteSegment { } @override - Parser compileNext(Parser p, bool isLast) { + Parser compileNext(Parser p, bool isLast) { return p.then(_compile()).map((r) { return (r.value![0] as RouteResult) ..addAll({name: Uri.decodeComponent(r.value![1] as String)}); diff --git a/packages/route/lib/src/route.dart b/packages/route/lib/src/route.dart index dd943015..aa5fb17e 100644 --- a/packages/route/lib/src/route.dart +++ b/packages/route/lib/src/route.dart @@ -2,20 +2,27 @@ part of angel_route.src.router; /// Represents a virtual location within an application. class Route { - final String? method; - final String path; - final List? handlers; + String method; + String path; final Map> _cache = {}; - final RouteDefinition? _routeDefinition; - String? name; - Parser? _parser; + String name = ''; + Parser? _parser; + late RouteDefinition _routeDefinition; + late List handlers; - Route(this.path, {required this.method, required this.handlers}) - : _routeDefinition = RouteGrammar.routeDefinition - .parse(SpanScanner(path.replaceAll(_straySlashes, '')))! - .value { - if (_routeDefinition?.segments.isNotEmpty != true) { - _parser = match('').map((r) => RouteResult({})); + Route(this.path, {required this.method, required this.handlers}) { + var result = RouteGrammar.routeDefinition + .parse(SpanScanner(path.replaceAll(_straySlashes, ''))); + + if (result.value != null) { + //throw ArgumentError('[Route] Failed to create route for $path'); + _routeDefinition = result.value!; + + if (_routeDefinition.segments.isEmpty) { + _parser = match('').map((r) => RouteResult({})); + } + } else { + //print('[Route] Failed to create route for $path'); } } @@ -26,7 +33,9 @@ class Route { method: b.method, handlers: b.handlers); } - Parser? get parser => _parser ??= _routeDefinition!.compile(); + //List get handlers => _handlers; + + Parser? get parser => _parser ??= _routeDefinition.compile(); @override String toString() { @@ -40,9 +49,9 @@ class Route { String makeUri(Map params) { var b = StringBuffer(); - int i = 0; + var i = 0; - for (var seg in _routeDefinition!.segments) { + for (var seg in _routeDefinition.segments) { if (i++ > 0) b.write('/'); if (seg is ConstantSegment) { b.write(seg.text); @@ -50,7 +59,7 @@ class Route { if (!params.containsKey(seg.name)) { throw ArgumentError('Missing parameter "${seg.name}".'); } - b.write(params[seg.name!]); + b.write(params[seg.name]); } } @@ -61,7 +70,7 @@ class Route { /// The result of matching an individual route. class RouteResult { /// The parsed route parameters. - final Map params; + final Map params; /// Optional. An explicit "tail" value to set. String? get tail => _tail; @@ -73,7 +82,7 @@ class RouteResult { void _setTail(String? v) => _tail ??= v; /// Adds parameters. - void addAll(Map map) { + void addAll(Map map) { params.addAll(map); } } diff --git a/packages/route/lib/src/router.dart b/packages/route/lib/src/router.dart index 986844d4..24a8404c 100644 --- a/packages/route/lib/src/router.dart +++ b/packages/route/lib/src/router.dart @@ -2,7 +2,6 @@ library angel_route.src.router; import 'dart:async'; import 'package:combinator/combinator.dart'; -import 'package:meta/meta.dart'; import 'package:string_scanner/string_scanner.dart'; import '../string_util.dart'; @@ -65,9 +64,8 @@ class Router { /// Adds a route that responds to the given path /// for requests with the given method (case-insensitive). /// Provide '*' as the method to respond to all methods. - Route addRoute(String? method, String path, T handler, + Route addRoute(String method, String path, T handler, {Iterable? middleware}) { - middleware ??= []; if (_useCache == true) { throw StateError('Cannot add routes after caching is enabled.'); } @@ -75,7 +73,10 @@ class Router { // Check if any mounted routers can match this final handlers = [handler]; - if (middleware != null) handlers.insertAll(0, middleware); + //middleware = []; + middleware ??= []; + + handlers.insertAll(0, middleware); final route = Route(path, method: method, handlers: handlers); _routes.add(route); @@ -115,29 +116,29 @@ class Router { /// Creates a visual representation of the route hierarchy and /// passes it to a callback. If none is provided, `print` is called. void dumpTree( - {callback(String tree)?, + {Function(String tree)? callback, String header = 'Dumping route tree:', String tab = ' '}) { final buf = StringBuffer(); - int tabs = 0; + var tabs = 0; - if (header != null && header.isNotEmpty) { + if (header.isNotEmpty) { buf.writeln(header); } buf.writeln(''); - indent() { - for (int i = 0; i < tabs; i++) { + void indent() { + for (var i = 0; i < tabs; i++) { buf.write(tab); } } - dumpRouter(Router router) { + void dumpRouter(Router router) { indent(); tabs++; - for (Route route in router.routes) { + for (var route in router.routes) { indent(); buf.write('- '); if (route is! SymlinkRoute) buf.write('${route.method} '); @@ -147,7 +148,7 @@ class Router { buf.writeln(); dumpRouter(route.router); } else { - buf.writeln(' => ${route.handlers!.length} handler(s)'); + buf.writeln(' => ${route.handlers.length} handler(s)'); } } @@ -164,9 +165,8 @@ class Router { /// /// Returns the created route. /// You can also register middleware within the router. - SymlinkRoute group(String path, void callback(Router router), - {Iterable? middleware, String? name}) { - middleware ??= []; + SymlinkRoute group(String path, void Function(Router router) callback, + {Iterable middleware = const Iterable.empty(), String name = ''}) { final router = Router().._middleware.addAll(middleware); callback(router); return mount(path, router)..name = name; @@ -174,9 +174,9 @@ class Router { /// Asynchronous equivalent of [group]. Future> groupAsync( - String path, FutureOr callback(Router router), - {Iterable? middleware, String? name}) async { - middleware ??= []; + String path, FutureOr Function(Router router) callback, + {Iterable middleware = const Iterable.empty(), + String name = ''}) async { final router = Router().._middleware.addAll(middleware); await callback(router); return mount(path, router)..name = name; @@ -210,16 +210,16 @@ class Router { /// router.navigate(['users/:id', {'id': '1337'}, 'profile']); /// ``` String navigate(Iterable linkParams, {bool absolute = true}) { - final List segments = []; + final segments = []; Router search = this; Route? lastRoute; for (final param in linkParams) { - bool resolved = false; + var resolved = false; if (param is String) { // Search by name - for (Route route in search.routes) { + for (var route in search.routes) { if (route.name == param) { segments.add(route.path.replaceAll(_straySlashes, '')); lastRoute = route; @@ -236,18 +236,23 @@ class Router { // Search by path if (!resolved) { var scanner = SpanScanner(param.replaceAll(_straySlashes, '')); - for (Route route in search.routes) { - int pos = scanner.position; - if (route.parser!.parse(scanner)!.successful && scanner.isDone) { - segments.add(route.path.replaceAll(_straySlashes, '')); - lastRoute = route; + for (var route in search.routes) { + var pos = scanner.position; + var parseResult = route.parser?.parse(scanner); + if (parseResult != null) { + if (parseResult.successful && scanner.isDone) { + segments.add(route.path.replaceAll(_straySlashes, '')); + lastRoute = route; - if (route is SymlinkRoute) { - search = route.router; + if (route is SymlinkRoute) { + search = route.router; + } + + resolved = true; + break; + } else { + scanner.position = pos; } - - resolved = true; - break; } else { scanner.position = pos; } @@ -281,39 +286,44 @@ class Router { /// Finds the first [Route] that matches the given path, /// with the given method. - bool resolve(String? absolute, String? relative, List> out, + bool resolve(String absolute, String relative, List> out, {String method = 'GET', bool strip = true}) { final cleanRelative = - strip == false ? relative! : stripStraySlashes(relative!); + strip == false ? relative : stripStraySlashes(relative); var scanner = SpanScanner(cleanRelative); bool crawl(Router r) { - bool success = false; + var success = false; for (var route in r.routes) { - int pos = scanner.position; + var pos = scanner.position; if (route is SymlinkRoute) { - if (route.parser!.parse(scanner)!.successful) { - var s = crawl(route.router); - if (s) success = true; + if (route.parser != null) { + var pp = route.parser!; + if (pp.parse(scanner).successful) { + var s = crawl(route.router); + if (s) success = true; + } } scanner.position = pos; } else if (route.method == '*' || route.method == method) { - var parseResult = route.parser!.parse(scanner)!; - - if (parseResult.successful && scanner.isDone) { - var result = RoutingResult( - parseResult: parseResult, - params: parseResult.value!.params, - shallowRoute: route, - shallowRouter: this, - tail: (parseResult.value!.tail ?? '') + scanner.rest); - out.add(result); - success = true; + var parseResult = route.parser?.parse(scanner); + if (parseResult != null) { + if (parseResult.successful && scanner.isDone) { + var tailResult = parseResult.value?.tail ?? ''; + print(tailResult); + var result = RoutingResult( + parseResult: parseResult, + params: parseResult.value!.params, + shallowRoute: route, + shallowRouter: this, + tail: tailResult + scanner.rest); + out.add(result); + success = true; + } } - scanner.position = pos; } } @@ -326,13 +336,13 @@ class Router { /// Returns the result of [resolve] with [path] passed as /// both `absolute` and `relative`. - Iterable> resolveAbsolute(String? path, + Iterable> resolveAbsolute(String path, {String method = 'GET', bool strip = true}) => resolveAll(path, path, method: method, strip: strip); /// Finds every possible [Route] that matches the given path, /// with the given method. - Iterable> resolveAll(String? absolute, String? relative, + Iterable> resolveAll(String absolute, String relative, {String method = 'GET', bool strip = true}) { if (_useCache == true) { return _cache.putIfAbsent('$method$absolute', @@ -342,7 +352,7 @@ class Router { return _resolveAll(absolute, relative, method: method, strip: strip); } - Iterable> _resolveAll(String? absolute, String? relative, + Iterable> _resolveAll(String absolute, String relative, {String method = 'GET', bool strip = true}) { var results = >[]; resolve(absolute, relative, results, method: method, strip: strip); @@ -363,88 +373,95 @@ class Router { } /// Adds a route that responds to any request matching the given path. - Route all(String path, T handler, {Iterable? middleware}) { + Route all(String path, T handler, + {Iterable middleware = const Iterable.empty()}) { return addRoute('*', path, handler, middleware: middleware); } /// Adds a route that responds to a DELETE request. - Route delete(String path, T handler, {Iterable? middleware}) { + Route delete(String path, T handler, + {Iterable middleware = const Iterable.empty()}) { return addRoute('DELETE', path, handler, middleware: middleware); } /// Adds a route that responds to a GET request. - Route get(String path, T handler, {Iterable? middleware}) { + Route get(String path, T handler, + {Iterable middleware = const Iterable.empty()}) { return addRoute('GET', path, handler, middleware: middleware); } /// Adds a route that responds to a HEAD request. - Route head(String path, T handler, {Iterable? middleware}) { + Route head(String path, T handler, + {Iterable middleware = const Iterable.empty()}) { return addRoute('HEAD', path, handler, middleware: middleware); } /// Adds a route that responds to a OPTIONS request. - Route options(String path, T handler, {Iterable? middleware}) { + Route options(String path, T handler, + {Iterable middleware = const Iterable.empty()}) { return addRoute('OPTIONS', path, handler, middleware: middleware); } /// Adds a route that responds to a POST request. - Route post(String path, T handler, {Iterable? middleware}) { + Route post(String path, T handler, + {Iterable middleware = const Iterable.empty()}) { return addRoute('POST', path, handler, middleware: middleware); } /// Adds a route that responds to a PATCH request. - Route patch(String path, T handler, {Iterable? middleware}) { + Route patch(String path, T handler, + {Iterable middleware = const Iterable.empty()}) { return addRoute('PATCH', path, handler, middleware: middleware); } /// Adds a route that responds to a PUT request. - Route put(String path, T handler, {Iterable? middleware}) { + Route put(String path, T handler, + {Iterable middleware = const Iterable.empty()}) { return addRoute('PUT', path, handler, middleware: middleware); } } class _ChainedRouter extends Router { final List _handlers = []; - Router? _root; + Router _root; - _ChainedRouter.empty(); + _ChainedRouter.empty() : _root = Router(); - _ChainedRouter(Router? root, Iterable middleware) { - this._root = root; + _ChainedRouter(this._root, Iterable middleware) { _handlers.addAll(middleware); } @override - Route addRoute(String? method, String path, handler, + Route addRoute(String method, String path, handler, {Iterable? middleware}) { - Route route = super.addRoute(method, path, handler, - middleware: []..addAll(_handlers)..addAll(middleware ?? [])); + middleware ??= []; + var route = super.addRoute(method, path, handler, + middleware: [..._handlers, ...middleware]); //_root._routes.add(route); return route; } @override - SymlinkRoute group(String path, void callback(Router router), - {Iterable? middleware, String? name}) { - final router = _ChainedRouter( - _root, []..addAll(_handlers)..addAll(middleware ?? [])); + SymlinkRoute group(String path, void Function(Router router) callback, + {Iterable middleware = const Iterable.empty(), String name = ''}) { + final router = _ChainedRouter(_root, [..._handlers, ...middleware]); callback(router); return mount(path, router)..name = name; } @override Future> groupAsync( - String path, FutureOr callback(Router router), - {Iterable? middleware, String? name}) async { - final router = _ChainedRouter( - _root, []..addAll(_handlers)..addAll(middleware ?? [])); + String path, FutureOr Function(Router router) callback, + {Iterable middleware = const Iterable.empty(), + String name = ''}) async { + final router = _ChainedRouter(_root, [..._handlers, ...middleware]); await callback(router); return mount(path, router)..name = name; } @override SymlinkRoute mount(String path, Router router) { - final SymlinkRoute route = super.mount(path, router); + final route = super.mount(path, router); route.router._middleware.insertAll(0, _handlers); //_root._routes.add(route); return route; @@ -453,7 +470,7 @@ class _ChainedRouter extends Router { @override _ChainedRouter chain(Iterable middleware) { final piped = _ChainedRouter.empty().._root = _root; - piped._handlers.addAll([]..addAll(_handlers)..addAll(middleware)); + piped._handlers.addAll([..._handlers, ...middleware]); var route = SymlinkRoute('/', piped); _routes.add(route); return piped; @@ -473,13 +490,13 @@ Router flatten(Router router) { var path = route.path.replaceAll(_straySlashes, ''); var joined = '$base/$path'.replaceAll(_straySlashes, ''); flattened.addRoute(route.method, joined.replaceAll(_straySlashes, ''), - route.handlers!.last, + route.handlers.last, middleware: - route.handlers!.take(route.handlers!.length - 1).toList()); + route.handlers.take(route.handlers.length - 1).toList()); } } else { - flattened.addRoute(route.method, route.path, route.handlers!.last, - middleware: route.handlers!.take(route.handlers!.length - 1).toList()); + flattened.addRoute(route.method, route.path, route.handlers.last, + middleware: route.handlers.take(route.handlers.length - 1).toList()); } } diff --git a/packages/route/lib/src/routing_result.dart b/packages/route/lib/src/routing_result.dart index 03ec4f73..351d235b 100644 --- a/packages/route/lib/src/routing_result.dart +++ b/packages/route/lib/src/routing_result.dart @@ -3,23 +3,23 @@ part of angel_route.src.router; /// Represents a complex result of navigating to a path. class RoutingResult { /// The parse result that matched the given sub-path. - final ParseResult? parseResult; + final ParseResult parseResult; /// A nested instance, if a sub-path was matched. - final Iterable>? nested; + final Iterable> nested; /// All route params matching this route on the current sub-path. - final Map params = {}; + final Map params = {}; /// The [Route] that answered this sub-path. /// /// This is mostly for internal use, and useless in production. - final Route? shallowRoute; + final Route shallowRoute; /// The [Router] that answered this sub-path. /// /// Only really for internal use. - final Router? shallowRouter; + final Router shallowRouter; /// The remainder of the full path that was not matched, and was passed to [nested] routes. final String tail; @@ -28,22 +28,22 @@ class RoutingResult { RoutingResult get deepest { var search = this; - while (search.nested?.isNotEmpty == true) { - search = search.nested!.first; + while (search.nested.isNotEmpty == true) { + search = search.nested.first; } return search; } /// The most specific route. - Route? get route => deepest.shallowRoute; + Route get route => deepest.shallowRoute; /// The most specific router. - Router? get router => deepest.shallowRouter; + Router get router => deepest.shallowRouter; /// The handlers at this sub-path. List get handlers { - return [...shallowRouter!.middleware, ...shallowRoute!.handlers!]; + return [...shallowRouter.middleware, ...shallowRoute.handlers]; } /// All handlers on this sub-path and its children. @@ -53,8 +53,8 @@ class RoutingResult { void crawl(RoutingResult result) { handlers.addAll(result.handlers); - if (result.nested?.isNotEmpty == true) { - for (var r in result.nested!) { + if (result.nested.isNotEmpty == true) { + for (var r in result.nested) { crawl(r); } } @@ -66,14 +66,14 @@ class RoutingResult { } /// All parameters on this sub-path and its children. - Map get allParams { - final params = {}; + Map get allParams { + final params = {}; void crawl(RoutingResult result) { params.addAll(result.params); - if (result.nested?.isNotEmpty == true) { - for (var r in result.nested!) { + if (result.nested.isNotEmpty == true) { + for (var r in result.nested) { crawl(r); } } @@ -84,11 +84,11 @@ class RoutingResult { } RoutingResult( - {this.parseResult, - Map params = const {}, - this.nested, - this.shallowRoute, - this.shallowRouter, + {required this.parseResult, + Map params = const {}, + this.nested = const Iterable.empty(), + required this.shallowRoute, + required this.shallowRouter, required this.tail}) { this.params.addAll(params); } diff --git a/packages/route/lib/src/symlink_route.dart b/packages/route/lib/src/symlink_route.dart index 9f49b369..a52d12de 100644 --- a/packages/route/lib/src/symlink_route.dart +++ b/packages/route/lib/src/symlink_route.dart @@ -5,5 +5,5 @@ part of angel_route.src.router; class SymlinkRoute extends Route { final Router router; SymlinkRoute(String path, this.router) - : super(path, method: null, handlers: null); + : super(path, method: 'GET', handlers: []); } diff --git a/packages/route/lib/string_util.dart b/packages/route/lib/string_util.dart index df2c8e2b..d09266ad 100644 --- a/packages/route/lib/string_util.dart +++ b/packages/route/lib/string_util.dart @@ -17,7 +17,7 @@ String stripStray(String haystack, String needle) { } // Find last leading index of slash - for (int i = firstSlash + 1; i < haystack.length; i++) { + for (var i = firstSlash + 1; i < haystack.length; i++) { if (haystack[i] != needle) { var sub = haystack.substring(i); @@ -25,7 +25,7 @@ String stripStray(String haystack, String needle) { var lastSlash = sub.lastIndexOf(needle); - for (int j = lastSlash - 1; j >= 0; j--) { + for (var j = lastSlash - 1; j >= 0; j--) { if (sub[j] != needle) { return sub.substring(0, j + 1); } diff --git a/packages/route/test/chain_nest_test.dart b/packages/route/test/chain_nest_test.dart index 64c5b75f..b4a62ee7 100644 --- a/packages/route/test/chain_nest_test.dart +++ b/packages/route/test/chain_nest_test.dart @@ -11,7 +11,7 @@ void main() { ..dumpTree(); test('nested route groups with chain', () { - var r = router.resolveAbsolute('/b/e/f').first.route!; + var r = router.resolveAbsolute('/b/e/f').first.route; expect(r, isNotNull); expect(r.handlers, hasLength(4)); expect(r.handlers, equals(['a', 'c', 'd', 'g'])); diff --git a/packages/route/test/navigate_test.dart b/packages/route/test/navigate_test.dart index b68273fe..37e29e74 100644 --- a/packages/route/test/navigate_test.dart +++ b/packages/route/test/navigate_test.dart @@ -8,7 +8,7 @@ void main() { router.get('/user/:id', 'GET'); router.get('/first/:first/last/:last', 'GET').name = 'full_name'; - navigate(params) { + String navigate(params) { final uri = router.navigate(params as Iterable); print('Uri: $uri'); return uri; diff --git a/packages/route/test/wildcard_test.dart b/packages/route/test/wildcard_test.dart index e974d51b..a49cfcf1 100644 --- a/packages/route/test/wildcard_test.dart +++ b/packages/route/test/wildcard_test.dart @@ -26,21 +26,21 @@ void main() { test('tail explicitly set intermediate', () { var results = router.resolveAbsolute('/songs/in_the/key'); var result = results.first; - print(results.map((r) => {r.route!.path: r.tail})); + print(results.map((r) => {r.route.path: r.tail})); expect(result.tail, 'in_the'); }); test('tail explicitly set at end', () { var results = router.resolveAbsolute('/isnt/she/epic'); var result = results.first; - print(results.map((r) => {r.route!.path: r.tail})); + print(results.map((r) => {r.route.path: r.tail})); expect(result.tail, 'epic'); }); test('tail with trailing', () { var results = router.resolveAbsolute('/isnt/she/epic/fail'); var result = results.first; - print(results.map((r) => {r.route!.path: r.tail})); + print(results.map((r) => {r.route.path: r.tail})); expect(result.tail, 'epic/fail'); }); } diff --git a/packages/route/web/shared/basic.dart b/packages/route/web/shared/basic.dart index 3f00c120..ff3bbebb 100644 --- a/packages/route/web/shared/basic.dart +++ b/packages/route/web/shared/basic.dart @@ -1,25 +1,30 @@ import 'dart:html'; import 'package:angel_route/browser.dart'; -basic(BrowserRouter router) { +void basic(BrowserRouter router) { final $h1 = window.document.querySelector('h1'); final $ul = window.document.getElementById('handlers'); router.onResolve.listen((result) { - final route = result?.route; + final route = result.route; - if (route == null) { - $h1!.text = 'No Active Route'; - $ul!.children + // TODO: Relook at this logic + //if (route == null) { + // $h1!.text = 'No Active Route'; + // $ul!.children + // ..clear() + // ..add(LIElement()..text = '(empty)'); + //} else { + if ($h1 != null && $ul != null) { + $h1.text = 'Active Route: ${route.name}'; + $ul.children ..clear() - ..add(LIElement()..text = '(empty)'); - } else { - $h1!.text = 'Active Route: ${route.name ?? route.path}'; - $ul!.children - ..clear() - ..addAll(result!.allHandlers + ..addAll(result.allHandlers .map((handler) => LIElement()..text = handler.toString())); + } else { + print('No active Route'); } + //} }); router.get('a', 'a handler'); From 22e073eb3f4b50621a93ab25f99476ed08e909b6 Mon Sep 17 00:00:00 2001 From: thomashii Date: Sat, 3 Apr 2021 23:04:25 +0800 Subject: [PATCH 027/171] Fixed route crashing --- packages/route/lib/browser.dart | 2 +- packages/route/lib/src/route.dart | 47 +++++++++++++++++++------------ 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/packages/route/lib/browser.dart b/packages/route/lib/browser.dart index dae09d2f..e73bc227 100644 --- a/packages/route/lib/browser.dart +++ b/packages/route/lib/browser.dart @@ -182,7 +182,7 @@ class _PushStateRouter extends _BrowserRouterImpl { // _onRoute.add(_current = null); //} else { final route = resolved.route; - var thisPath = route.name; + var thisPath = route.name ?? ''; if (thisPath.isEmpty) { thisPath = route.path; } diff --git a/packages/route/lib/src/route.dart b/packages/route/lib/src/route.dart index aa5fb17e..ad014205 100644 --- a/packages/route/lib/src/route.dart +++ b/packages/route/lib/src/route.dart @@ -2,28 +2,37 @@ part of angel_route.src.router; /// Represents a virtual location within an application. class Route { - String method; - String path; + final String method; + final String path; final Map> _cache = {}; - String name = ''; + final RouteDefinition? _routeDefinition; + final List handlers; + String? name; Parser? _parser; - late RouteDefinition _routeDefinition; - late List handlers; - Route(this.path, {required this.method, required this.handlers}) { + Route(this.path, {required this.method, required this.handlers}) + : _routeDefinition = RouteGrammar.routeDefinition + .parse(SpanScanner(path.replaceAll(_straySlashes, ''))) + .value { + if (_routeDefinition?.segments.isNotEmpty != true) { + _parser = match('').map((r) => RouteResult({})); + } + + /* var result = RouteGrammar.routeDefinition .parse(SpanScanner(path.replaceAll(_straySlashes, ''))); if (result.value != null) { + //throw ArgumentError('[Route] Failed to create route for $path'); - _routeDefinition = result.value!; - + _routeDefinition = result.value; if (_routeDefinition.segments.isEmpty) { _parser = match('').map((r) => RouteResult({})); } } else { - //print('[Route] Failed to create route for $path'); + _parser = match('').map((r) => RouteResult({})); } + */ } factory Route.join(Route a, Route b) { @@ -35,7 +44,7 @@ class Route { //List get handlers => _handlers; - Parser? get parser => _parser ??= _routeDefinition.compile(); + Parser? get parser => _parser ??= _routeDefinition?.compile(); @override String toString() { @@ -51,15 +60,17 @@ class Route { var b = StringBuffer(); var i = 0; - for (var seg in _routeDefinition.segments) { - if (i++ > 0) b.write('/'); - if (seg is ConstantSegment) { - b.write(seg.text); - } else if (seg is ParameterSegment) { - if (!params.containsKey(seg.name)) { - throw ArgumentError('Missing parameter "${seg.name}".'); + if (_routeDefinition != null) { + for (var seg in _routeDefinition!.segments) { + if (i++ > 0) b.write('/'); + if (seg is ConstantSegment) { + b.write(seg.text); + } else if (seg is ParameterSegment) { + if (!params.containsKey(seg.name)) { + throw ArgumentError('Missing parameter "${seg.name}".'); + } + b.write(params[seg.name]); } - b.write(params[seg.name]); } } From d65a0e7c837ec7195015aefd3b2aea0d144bba54 Mon Sep 17 00:00:00 2001 From: thomashii Date: Sun, 4 Apr 2021 22:26:58 +0800 Subject: [PATCH 028/171] Framework migration update --- .../lib/src/core/anonymous_service.dart | 28 ++-- .../framework/lib/src/core/controller.dart | 24 +-- packages/framework/lib/src/core/driver.dart | 32 ++-- .../lib/src/core/hooked_service.dart | 14 +- .../lib/src/core/hostname_parser.dart | 2 +- .../lib/src/core/hostname_router.dart | 44 +++--- .../framework/lib/src/core/injection.dart | 30 ++-- .../framework/lib/src/core/map_service.dart | 6 +- packages/framework/lib/src/core/metadata.dart | 8 +- .../lib/src/core/request_context.dart | 143 +++++++++++------- packages/framework/lib/src/core/routable.dart | 10 +- packages/framework/lib/src/core/server.dart | 33 ++-- packages/framework/lib/src/core/service.dart | 33 ++-- .../framework/lib/src/http/angel_http.dart | 17 +-- .../lib/src/http/http_request_context.dart | 20 +-- .../lib/src/http/http_response_context.dart | 4 +- .../framework/lib/src/http2/angel_http2.dart | 19 ++- .../lib/src/http2/http2_request_context.dart | 23 ++- .../lib/src/http2/http2_response_context.dart | 4 +- packages/framework/test/find_one_test.dart | 2 + .../framework/test/http2/adapter_test.dart | 4 +- .../framework/test/http2/http2_client.dart | 2 +- packages/framework/test/routing_test.dart | 2 + packages/route/lib/src/router.dart | 32 ++-- 24 files changed, 288 insertions(+), 248 deletions(-) diff --git a/packages/framework/lib/src/core/anonymous_service.dart b/packages/framework/lib/src/core/anonymous_service.dart index 185d8a9a..7b2ad5f7 100644 --- a/packages/framework/lib/src/core/anonymous_service.dart +++ b/packages/framework/lib/src/core/anonymous_service.dart @@ -7,19 +7,19 @@ import 'service.dart'; /// /// Well-suited for testing. class AnonymousService extends Service { - FutureOr>? Function([Map?])? _index; - FutureOr Function(Id?, [Map?])? _read, _remove; + FutureOr> Function([Map?])? _index; + FutureOr Function(Id, [Map?])? _read, _remove; FutureOr Function(Data, [Map?])? _create; - FutureOr Function(Id?, Data, [Map?])? _modify, _update; + FutureOr Function(Id, Data, [Map?])? _modify, _update; AnonymousService( - {FutureOr>? index([Map? params])?, - FutureOr read(Id? id, [Map? params])?, + {FutureOr> index([Map? params])?, + FutureOr read(Id id, [Map? params])?, FutureOr create(Data data, [Map? params])?, - FutureOr modify(Id? id, Data data, [Map? params])?, - FutureOr update(Id? id, Data data, [Map? params])?, - FutureOr remove(Id? id, [Map? params])?, - FutureOr Function(RequestContext, ResponseContext?)? readData}) + FutureOr modify(Id id, Data data, [Map? params])?, + FutureOr update(Id id, Data data, [Map? params])?, + FutureOr remove(Id id, [Map? params])?, + FutureOr Function(RequestContext, ResponseContext)? readData}) : super(readData: readData) { _index = index; _read = read; @@ -31,10 +31,10 @@ class AnonymousService extends Service { @override index([Map? params]) => - Future.sync(() => _index != null ? _index!(params)! : super.index(params)); + Future.sync(() => _index != null ? _index!(params) : super.index(params)); @override - read(Id? id, [Map? params]) => Future.sync( + read(Id id, [Map? params]) => Future.sync( () => _read != null ? _read!(id, params) : super.read(id, params)); @override @@ -42,18 +42,18 @@ class AnonymousService extends Service { _create != null ? _create!(data, params) : super.create(data, params)); @override - modify(Id? id, Data data, [Map? params]) => + modify(Id id, Data data, [Map? params]) => Future.sync(() => _modify != null ? _modify!(id, data, params) : super.modify(id, data, params)); @override - update(Id? id, Data data, [Map? params]) => + update(Id id, Data data, [Map? params]) => Future.sync(() => _update != null ? _update!(id, data, params) : super.update(id, data, params)); @override - remove(Id? id, [Map? params]) => Future.sync( + remove(Id id, [Map? params]) => Future.sync( () => _remove != null ? _remove!(id, params) : super.remove(id, params)); } diff --git a/packages/framework/lib/src/core/controller.dart b/packages/framework/lib/src/core/controller.dart index d1a83ba6..e129e5a9 100644 --- a/packages/framework/lib/src/core/controller.dart +++ b/packages/framework/lib/src/core/controller.dart @@ -21,12 +21,12 @@ class Controller { List middleware = []; /// A mapping of route paths to routes, produced from the [Expose] annotations on this class. - Map routeMappings = {}; + Map routeMappings = {}; - SymlinkRoute? _mountPoint; + SymlinkRoute? _mountPoint; /// The route at which this controller is mounted on the server. - SymlinkRoute? get mountPoint => _mountPoint; + SymlinkRoute? get mountPoint => _mountPoint; Controller({this.injectSingleton = true}); @@ -47,8 +47,8 @@ class Controller { } /// Applies the routes from this [Controller] to some [router]. - Future applyRoutes( - Router router, Reflector reflector) async { + Future applyRoutes( + Router router, Reflector reflector) async { // Load global expose decl var classMirror = reflector.reflectClass(this.runtimeType)!; Expose? exposeDecl = findExpose(reflector); @@ -58,7 +58,8 @@ class Controller { } var routable = Routable(); - _mountPoint = router.mount(exposeDecl.path!, routable); + var m = router.mount(exposeDecl.path!, routable); + _mountPoint = m; var typeMirror = reflector.reflectType(this.runtimeType); // Pre-reflect methods @@ -72,7 +73,10 @@ class Controller { classMirror.declarations.forEach(routeBuilder); // Return the name. - return exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : typeMirror!.name; + var result = + exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : typeMirror!.name; + + return Future.value(result); } void Function(ReflectedDeclaration) _routeBuilder( @@ -117,8 +121,8 @@ class Controller { method.parameters[0].type.reflectedType == RequestContext && method.parameters[1].type.reflectedType == ResponseContext) { // Create a regular route - routeMappings[name] = routable - .addRoute(exposeDecl.method, exposeDecl.path, + routeMappings[name ?? ''] = routable + .addRoute(exposeDecl.method, exposeDecl.path ?? '', (RequestContext req, ResponseContext res) { var result = reflectedMethod!(req, res); return result is RequestHandler ? result(req, res) : result; @@ -190,7 +194,7 @@ class Controller { if (!path.startsWith('/')) path = '/$path'; } - routeMappings[name] = routable.addRoute( + routeMappings[name ?? ''] = routable.addRoute( httpMethod, path, handleContained(reflectedMethod, injection), middleware: middleware); } diff --git a/packages/framework/lib/src/core/driver.dart b/packages/framework/lib/src/core/driver.dart index a38f5d7c..c52cc244 100644 --- a/packages/framework/lib/src/core/driver.dart +++ b/packages/framework/lib/src/core/driver.dart @@ -107,27 +107,27 @@ abstract class Driver< var path = req.path; if (path == '/') path = ''; - Tuple4, ParseResult?, + Tuple4, ParseResult, MiddlewarePipeline> resolveTuple() { var r = app.optimizedRouter; var resolved = - r.resolveAbsolute(path, method: req.method!, strip: false); - var pipeline = MiddlewarePipeline(resolved); + r.resolveAbsolute(path, method: req.method, strip: false); + var pipeline = MiddlewarePipeline(resolved); return Tuple4( - pipeline.handlers, - resolved.fold>( - {}, (out, r) => out..addAll(r.allParams)), - resolved.isEmpty ? null : resolved.first.parseResult, + pipeline.handlers!, + resolved.fold>( + {}, (out, r) => out..addAll(r.allParams)), + (resolved.isEmpty ? null : resolved.first.parseResult)!, pipeline, ); } - var cacheKey = req.method! + path!; + var cacheKey = req.method + path; var tuple = app.environment.isProduction ? app.handlerCache.putIfAbsent(cacheKey, resolveTuple) : resolveTuple(); - var line = tuple.item4 as MiddlewarePipeline; - var it = MiddlewarePipelineIterator(line); + var line = tuple.item4 as MiddlewarePipeline; + var it = MiddlewarePipelineIterator(line); req.params.addAll(tuple.item2); @@ -135,10 +135,10 @@ abstract class Driver< ?..registerSingleton(req) ..registerSingleton(res) ..registerSingleton(tuple.item4) - ..registerSingleton>(line) + ..registerSingleton>(line) ..registerSingleton(it) - ..registerSingleton>(it) - ..registerSingleton?>(tuple.item3) + ..registerSingleton>(it) + ..registerSingleton?>(tuple.item3) ..registerSingleton(tuple.item3); if (app.environment.isProduction && app.logger != null) { @@ -318,8 +318,8 @@ abstract class Driver< List outputBuffer = res.buffer!.toBytes(); if (res.encoders.isNotEmpty) { - var allowedEncodings = req.headers! - .value('accept-encoding') + var allowedEncodings = req.headers + ?.value('accept-encoding') ?.split(',') .map((s) => s.trim()) .where((s) => s.isNotEmpty) @@ -361,7 +361,7 @@ abstract class Driver< /// Runs a [MiddlewarePipeline]. static Future runPipeline( - MiddlewarePipelineIterator it, + MiddlewarePipelineIterator it, RequestContextType req, ResponseContextType res, Angel app) async { diff --git a/packages/framework/lib/src/core/hooked_service.dart b/packages/framework/lib/src/core/hooked_service.dart index 9b6f404f..2bd0e237 100644 --- a/packages/framework/lib/src/core/hooked_service.dart +++ b/packages/framework/lib/src/core/hooked_service.dart @@ -49,7 +49,7 @@ class HookedService> } @override - FutureOr? Function(RequestContext, ResponseContext?)? get readData => + FutureOr Function(RequestContext, ResponseContext)? get readData => inner.readData; RequestContext? _getRequest(Map? params) { @@ -296,7 +296,7 @@ class HookedService> } @override - Future read(Id? id, [Map? _params]) { + Future read(Id id, [Map? _params]) { var params = _stripReq(_params); return beforeRead ._emit(HookedServiceEvent(false, _getRequest(_params), @@ -322,7 +322,7 @@ class HookedService> } @override - Future create(Data? data, [Map? _params]) { + Future create(Data data, [Map? _params]) { var params = _stripReq(_params); return beforeCreated ._emit(HookedServiceEvent(false, _getRequest(_params), @@ -348,7 +348,7 @@ class HookedService> } @override - Future modify(Id? id, Data? data, [Map? _params]) { + Future modify(Id id, Data data, [Map? _params]) { var params = _stripReq(_params); return beforeModified ._emit(HookedServiceEvent(false, _getRequest(_params), @@ -377,7 +377,7 @@ class HookedService> } @override - Future update(Id? id, Data? data, [Map? _params]) { + Future update(Id id, Data data, [Map? _params]) { var params = _stripReq(_params); return beforeUpdated ._emit(HookedServiceEvent(false, _getRequest(_params), @@ -406,7 +406,7 @@ class HookedService> } @override - Future remove(Id? id, [Map? _params]) { + Future remove(Id id, [Map? _params]) { var params = _stripReq(_params); return beforeRemoved ._emit(HookedServiceEvent(false, _getRequest(_params), @@ -480,7 +480,7 @@ class HookedService> } /// Fired when a hooked service is invoked. -class HookedServiceEvent> { +class HookedServiceEvent> { static const String indexed = 'indexed'; static const String read = 'read'; static const String created = 'created'; diff --git a/packages/framework/lib/src/core/hostname_parser.dart b/packages/framework/lib/src/core/hostname_parser.dart index 86525757..32b38721 100644 --- a/packages/framework/lib/src/core/hostname_parser.dart +++ b/packages/framework/lib/src/core/hostname_parser.dart @@ -17,7 +17,7 @@ class HostnameSyntaxParser { RegExp parse() { var b = StringBuffer(); - var parts = Queue(); + var parts = Queue(); while (!_scanner.isDone) { if (_scanner.scan('|')) { diff --git a/packages/framework/lib/src/core/hostname_router.dart b/packages/framework/lib/src/core/hostname_router.dart index c568287c..06cbe9be 100644 --- a/packages/framework/lib/src/core/hostname_router.dart +++ b/packages/framework/lib/src/core/hostname_router.dart @@ -84,30 +84,28 @@ class HostnameRouter { /// Also returns `true` if all of the sub-app's handlers returned /// `true`. Future handleRequest(RequestContext req, ResponseContext res) async { - if (req.hostname != null) { - for (var pattern in _patterns) { - // print('${req.hostname} vs $_creators'); - if (pattern.allMatches(req.hostname!).isNotEmpty) { - // Resolve the entire pipeline within the context of the selected app. - var app = _apps[pattern] ??= (await _creators[pattern]!()); - // print('App for ${req.hostname} = $app from $pattern'); - // app.dumpTree(); + for (var pattern in _patterns) { + // print('${req.hostname} vs $_creators'); + if (pattern.allMatches(req.hostname).isNotEmpty) { + // Resolve the entire pipeline within the context of the selected app. + var app = _apps[pattern] ??= (await _creators[pattern]!()); + // print('App for ${req.hostname} = $app from $pattern'); + // app.dumpTree(); - var r = app.optimizedRouter; - var resolved = r.resolveAbsolute(req.path, method: req.method!); - var pipeline = MiddlewarePipeline(resolved); - // print('Pipeline: $pipeline'); - for (var handler in pipeline.handlers!) { - // print(handler); - // Avoid stack overflow. - if (handler == handleRequest) { - continue; - } else if (!await app.executeHandler(handler, req, res)) { - // print('$handler TERMINATED'); - return false; - } else { - // print('$handler CONTINUED'); - } + var r = app.optimizedRouter; + var resolved = r.resolveAbsolute(req.path, method: req.method); + var pipeline = MiddlewarePipeline(resolved); + // print('Pipeline: $pipeline'); + for (var handler in pipeline.handlers!) { + // print(handler); + // Avoid stack overflow. + if (handler == handleRequest) { + continue; + } else if (!await app.executeHandler(handler, req, res)) { + // print('$handler TERMINATED'); + return false; + } else { + // print('$handler CONTINUED'); } } } diff --git a/packages/framework/lib/src/core/injection.dart b/packages/framework/lib/src/core/injection.dart index 92ef801e..65698e5a 100644 --- a/packages/framework/lib/src/core/injection.dart +++ b/packages/framework/lib/src/core/injection.dart @@ -13,27 +13,31 @@ RequestHandler ioc(Function handler, {Iterable optional = const []}) { return (req, res) { if (injection == null) { - injection = preInject(handler, req.app.container!.reflector); - injection!.optional.addAll(optional); - contained = handleContained(handler, injection); + if (req.app!.container != null) { + injection = preInject(handler, req.app!.container!.reflector); + if (injection != null) { + injection?.optional.addAll(optional); + contained = handleContained(handler, injection!); + } + } } - return req.app.executeHandler(contained, req, res); + return req.app!.executeHandler(contained, req, res); }; } -resolveInjection(requirement, InjectionRequest? injection, RequestContext req, +resolveInjection(requirement, InjectionRequest injection, RequestContext req, ResponseContext res, bool throwOnUnresolved, [Container? container]) async { var propFromApp; - container ??= req.container ?? res.app?.container; + container ??= req.container ?? res.app!.container; if (requirement == RequestContext) { return req; } else if (requirement == ResponseContext) { return res; } else if (requirement is String && - injection!.parameters.containsKey(requirement)) { + injection.parameters.containsKey(requirement)) { var param = injection.parameters[requirement]!; var value = param.getValue(req); if (value == null && param.required != false) throw param.error as Object; @@ -44,9 +48,9 @@ resolveInjection(requirement, InjectionRequest? injection, RequestContext req, } if (req.params.containsKey(requirement)) { return req.params[requirement]; - } else if ((propFromApp = req.app.findProperty(requirement)) != null) { + } else if ((propFromApp = req.app!.findProperty(requirement)) != null) { return propFromApp; - } else if (injection!.optional.contains(requirement)) { + } else if (injection.optional.contains(requirement)) { return null; } else if (throwOnUnresolved) { throw ArgumentError( @@ -59,7 +63,7 @@ resolveInjection(requirement, InjectionRequest? injection, RequestContext req, var key = requirement.first; var type = requirement.last; if (req.params.containsKey(key) || - req.app.configuration.containsKey(key) || + req.app!.configuration.containsKey(key) || _primitiveTypes.contains(type)) { return await resolveInjection( key, injection, req, res, throwOnUnresolved, container); @@ -95,10 +99,10 @@ bool suitableForInjection( } /// Handles a request with a DI-enabled handler. -RequestHandler handleContained(Function? handler, InjectionRequest? injection, +RequestHandler handleContained(Function handler, InjectionRequest injection, [Container? container]) { return (RequestContext req, ResponseContext res) async { - if (injection!.parameters.isNotEmpty && + if (injection.parameters.isNotEmpty && injection.parameters.values.any((p) => p.match != null) && !suitableForInjection(req, res, injection)) return Future.value(true); @@ -116,7 +120,7 @@ RequestHandler handleContained(Function? handler, InjectionRequest? injection, [entry.key, entry.value], injection, req, res, false, container); } - return Function.apply(handler!, args, named); + return Function.apply(handler, args, named); }; } diff --git a/packages/framework/lib/src/core/map_service.dart b/packages/framework/lib/src/core/map_service.dart index d3a02aa8..79303306 100644 --- a/packages/framework/lib/src/core/map_service.dart +++ b/packages/framework/lib/src/core/map_service.dart @@ -67,10 +67,12 @@ class MapService extends Service> { } @override - Future> read(String? id, [Map? params]) { + Future> read(String? id, + [Map? params]) { return Future.value(items.firstWhere(_matchesId(id), orElse: (() => throw AngelHttpException.notFound( - message: 'No record found for ID $id')) as Map Function()?)); + message: 'No record found for ID $id')) + as Map Function()?)); } @override diff --git a/packages/framework/lib/src/core/metadata.dart b/packages/framework/lib/src/core/metadata.dart index 0012b340..5df2de39 100644 --- a/packages/framework/lib/src/core/metadata.dart +++ b/packages/framework/lib/src/core/metadata.dart @@ -122,16 +122,16 @@ class Parameter { /// Obtains a value for this parameter from a [RequestContext]. getValue(RequestContext req) { if (cookie?.isNotEmpty == true) { - return req.cookies!.firstWhere((c) => c.name == cookie).value; + return req.cookies.firstWhere((c) => c.name == cookie).value; } if (header?.isNotEmpty == true) { - return req.headers!.value(header!) ?? defaultValue; + return req.headers?.value(header ?? '') ?? defaultValue; } if (session?.isNotEmpty == true) { - return req.session![session] ?? defaultValue; + return req.session?[session] ?? defaultValue; } if (query?.isNotEmpty == true) { - return req.uri!.queryParameters[query!] ?? defaultValue; + return req.uri?.queryParameters[query] ?? defaultValue; } return defaultValue; } diff --git a/packages/framework/lib/src/core/request_context.dart b/packages/framework/lib/src/core/request_context.dart index 6fb2e974..9801bf21 100644 --- a/packages/framework/lib/src/core/request_context.dart +++ b/packages/framework/lib/src/core/request_context.dart @@ -18,6 +18,7 @@ import 'package:meta/meta.dart'; import 'package:mime/mime.dart'; import 'package:path/path.dart' as p; import 'package:collection/collection.dart'; +import 'package:logging/logging.dart'; import 'metadata.dart'; import 'response_context.dart'; @@ -30,15 +31,19 @@ part 'injection.dart'; abstract class RequestContext { /// Similar to [Angel.shutdownHooks], allows for logic to be executed /// when a [RequestContext] is done being processed. + final log = Logger('RequestContext'); + final List Function()> shutdownHooks = []; String? _acceptHeaderCache, _extensionCache; - bool? _acceptsAllCache, _hasParsedBody = false, _closed = false; - Map? _bodyFields, _queryParameters; - List? _bodyList; + bool? _acceptsAllCache; + Map? _queryParameters; Object? _bodyObject; - List? _uploadedFiles; - MediaType? _contentType; + bool _hasParsedBody = false, _closed = false; + Map _bodyFields = {}; + List _bodyList = []; + List _uploadedFiles = []; + MediaType _contentType = MediaType("text", "html"); /// The underlying [RawRequest] provided by the driver. RawRequest get rawRequest; @@ -47,16 +52,16 @@ abstract class RequestContext { final Map serviceParams = {}; /// The [Angel] instance that is responding to this request. - late Angel app; + Angel? app; /// Any cookies sent with this request. - List? get cookies; + List get cookies => []; /// All HTTP headers sent with this request. HttpHeaders? get headers; /// The requested hostname. - String? get hostname; + String get hostname => 'localhost'; /// The IoC container that can be used to provide functionality to produce /// objects of a given type. @@ -70,24 +75,33 @@ abstract class RequestContext { /// This request's HTTP method. /// /// This may have been processed by an override. See [originalMethod] to get the real method. - String? get method; + String get method => 'GET'; /// The original HTTP verb sent to the server. - String? get originalMethod; + String get originalMethod => 'GET'; /// The content type of an incoming request. - MediaType? get contentType => - _contentType ??= MediaType.parse(headers!.contentType.toString()); + MediaType get contentType { + if (headers?.contentType != null) { + try { + _contentType = MediaType.parse(headers!.contentType.toString()); + } catch (e) { + log.warning( + 'Invalid media type [${headers!.contentType.toString()}]', e); + } + } + return _contentType; + } /// The URL parameters extracted from the request URI. - Map params = {}; + Map params = {}; /// The requested path. - String? get path; + String get path => ''; /// Is this an **XMLHttpRequest**? bool get isXhr { - return headers!.value("X-Requested-With")?.trim()?.toLowerCase() == + return headers?.value("X-Requested-With")?.trim().toLowerCase() == 'xmlhttprequest'; } @@ -104,17 +118,18 @@ abstract class RequestContext { Stream>? get body; /// Returns `true` if [parseBody] has been called so far. - bool? get hasParsedBody => _hasParsedBody; + bool get hasParsedBody => _hasParsedBody; /// Returns a *mutable* [Map] of the fields parsed from the request [body]. /// /// Note that [parseBody] must be called first. - Map? get bodyAsMap { - if (!hasParsedBody!) { + Map get bodyAsMap { + if (!hasParsedBody) { throw StateError('The request body has not been parsed yet.'); - } else if (_bodyFields == null) { - throw StateError('The request body, $_bodyObject, is not a Map.'); } + // else if (_bodyFields == null) { + // throw StateError('The request body, $_bodyObject, is not a Map.'); + //} return _bodyFields; } @@ -122,13 +137,13 @@ abstract class RequestContext { /// This setter allows you to explicitly set the request body **exactly once**. /// /// Use this if the format of the body is not natively parsed by Angel. - set bodyAsMap(Map? value) => bodyAsObject = value; + set bodyAsMap(Map? value) => bodyAsObject = value; /// Returns a *mutable* [List] parsed from the request [body]. /// /// Note that [parseBody] must be called first. List? get bodyAsList { - if (!hasParsedBody!) { + if (!hasParsedBody) { throw StateError('The request body has not been parsed yet.'); } else if (_bodyList == null) { throw StateError('The request body, $_bodyObject, is not a List.'); @@ -146,7 +161,7 @@ abstract class RequestContext { /// /// Note that [parseBody] must be called first. Object? get bodyAsObject { - if (!hasParsedBody!) { + if (!hasParsedBody) { throw StateError('The request body has not been parsed yet.'); } @@ -162,7 +177,7 @@ abstract class RequestContext { 'The request body has already been parsed/set, and cannot be overwritten.'); } else { if (value is List) _bodyList = value; - if (value is Map) _bodyFields = value; + if (value is Map) _bodyFields = value; _bodyObject = value; _hasParsedBody = true; } @@ -172,7 +187,7 @@ abstract class RequestContext { /// /// Note that [parseBody] must be called first. List? get uploadedFiles { - if (!hasParsedBody!) { + if (!hasParsedBody) { throw StateError('The request body has not been parsed yet.'); } @@ -180,13 +195,13 @@ abstract class RequestContext { } /// Returns a *mutable* map of the fields contained in the query. - Map get queryParameters => - _queryParameters ??= Map.from(uri!.queryParameters); + Map get queryParameters => _queryParameters ??= + Map.from(uri?.queryParameters ?? {}); /// Returns the file extension of the requested path, if any. /// /// Includes the leading `.`, if there is one. - String get extension => _extensionCache ??= p.extension(uri!.path); + String get extension => _extensionCache ??= p.extension(uri?.path ?? ''); /// Returns `true` if the client's `Accept` header indicates that the given [contentType] is considered a valid response. /// @@ -208,7 +223,7 @@ abstract class RequestContext { 'RequestContext.accepts expects the `contentType` parameter to NOT be null.'); } - _acceptHeaderCache ??= headers!.value('accept'); + _acceptHeaderCache ??= headers?.value('accept'); if (_acceptHeaderCache == null) { return true; @@ -235,52 +250,58 @@ abstract class RequestContext { /// Manually parses the request body, if it has not already been parsed. Future parseBody({Encoding encoding = utf8}) async { - if (contentType == null) { - throw FormatException('Missing "content-type" header.'); - } + //if (contentType == null) { + // throw FormatException('Missing "content-type" header.'); + //} - if (!_hasParsedBody!) { + if (!_hasParsedBody) { _hasParsedBody = true; - if (contentType!.type == 'application' && - contentType!.subtype == 'json') { + var contentBody = body; + if (contentBody == null) { + contentBody = Stream.empty(); + } + + if (contentType.type == 'application' && contentType.subtype == 'json') { _uploadedFiles = []; var parsed = (_bodyObject = - await encoding.decoder.bind(body!).join().then(json.decode))!; + await encoding.decoder.bind(contentBody).join().then(json.decode)); if (parsed is Map) { - _bodyFields = Map.from(parsed); + _bodyFields = Map.from(parsed); } else if (parsed is List) { _bodyList = parsed; } - } else if (contentType!.type == 'application' && - contentType!.subtype == 'x-www-form-urlencoded') { + } else if (contentType.type == 'application' && + contentType.subtype == 'x-www-form-urlencoded') { _uploadedFiles = []; var parsed = await encoding.decoder - .bind(body!) + .bind(contentBody) .join() .then((s) => Uri.splitQueryString(s, encoding: encoding)); - _bodyFields = Map.from(parsed); - } else if (contentType!.type == 'multipart' && - contentType!.subtype == 'form-data' && - contentType!.parameters.containsKey('boundary')) { - var boundary = contentType!.parameters['boundary']!; + _bodyFields = Map.from(parsed); + } else if (contentType.type == 'multipart' && + contentType.subtype == 'form-data' && + contentType.parameters.containsKey('boundary')) { + var boundary = contentType.parameters['boundary'] ?? ''; var transformer = MimeMultipartTransformer(boundary); - var parts = transformer.bind(body!).map((part) => + var parts = transformer.bind(contentBody).map((part) => HttpMultipartFormData.parse(part, defaultEncoding: encoding)); _bodyFields = {}; _uploadedFiles = []; await for (var part in parts) { if (part.isBinary) { - _uploadedFiles!.add(UploadedFile(part)); + _uploadedFiles.add(UploadedFile(part)); } else if (part.isText && part.contentDisposition.parameters.containsKey('name')) { // If there is no name, then don't parse it. var key = part.contentDisposition.parameters['name']; - var value = await part.join(); - _bodyFields![key] = value; + if (key != null) { + var value = await part.join(); + _bodyFields[key] = value; + } } } } else { @@ -293,7 +314,7 @@ abstract class RequestContext { /// Disposes of all resources. @mustCallSuper Future close() async { - if (!_closed!) { + if (!_closed) { _closed = true; _acceptsAllCache = null; _acceptHeaderCache = null; @@ -308,8 +329,9 @@ abstract class RequestContext { class UploadedFile { /// The underlying `form-data` item. final HttpMultipartFormData formData; + final log = Logger('UploadedFile'); - MediaType? _contentType; + MediaType _contentType = MediaType("multipart", "form-data"); UploadedFile(this.formData); @@ -326,9 +348,22 @@ class UploadedFile { /// The parsed [:Content-Type:] header of the [:HttpMultipartFormData:]. /// Returns [:null:] if not present. - MediaType? get contentType => _contentType ??= (formData.contentType == null - ? null - : MediaType.parse(formData.contentType.toString())); + //MediaType get contentType => _contentType ??= (formData.contentType == null + // ? null + // : MediaType.parse(formData.contentType.toString())); + + MediaType get contentType { + if (formData.contentType != null) { + try { + _contentType = MediaType.parse(formData.contentType.toString()); + } catch (e) { + log.warning( + 'Invalue media type [${formData.contentType.toString()}]', e); + } + } + + return _contentType; + } /// The parsed [:Content-Transfer-Encoding:] header of the /// [:HttpMultipartFormData:]. This field is used to determine how to decode diff --git a/packages/framework/lib/src/core/routable.dart b/packages/framework/lib/src/core/routable.dart index a52667ed..09ccfa5a 100644 --- a/packages/framework/lib/src/core/routable.dart +++ b/packages/framework/lib/src/core/routable.dart @@ -32,7 +32,7 @@ RequestHandler chain(Iterable handlers) { var current = runPipeline; runPipeline = () => current().then((result) => res.isOpen ? Future.value(result) - : req.app.executeHandler(handler, req, res)); + : req.app!.executeHandler(handler, req, res)); } } @@ -73,7 +73,7 @@ class Routable extends Router { Stream get onService => _onService.stream; /// Retrieves the service assigned to the given path. - T? findService(Pattern path) { + T? findService(Pattern path) { return _serviceLookups.putIfAbsent(path, () { return _services[path] ?? _services[path.toString().replaceAll(_straySlashes, '')]; @@ -94,7 +94,7 @@ class Routable extends Router { @override Route addRoute( String method, String path, RequestHandler handler, - {Iterable middleware = const Iterable.empty()}) { + {Iterable? middleware}) { final handlers = []; // Merge @Middleware declaration, if any var reflector = _container?.reflector; @@ -107,7 +107,9 @@ class Routable extends Router { } final handlerSequence = []; - handlerSequence.addAll(middleware); + if (middleware != null) { + handlerSequence.addAll(middleware); + } handlerSequence.addAll(handlers); return super.addRoute(method, path.toString(), handler, diff --git a/packages/framework/lib/src/core/server.dart b/packages/framework/lib/src/core/server.dart index 97ec3efa..969d966c 100644 --- a/packages/framework/lib/src/core/server.dart +++ b/packages/framework/lib/src/core/server.dart @@ -36,10 +36,10 @@ class Angel extends Routable { final List _children = []; final Map< String, - Tuple4, ParseResult?, + Tuple4, ParseResult?, MiddlewarePipeline>> handlerCache = HashMap(); - Router? _flattened; + Router? _flattened; Angel? _parent; /// A global Map of converters that can transform responses bodies. @@ -59,7 +59,7 @@ class Angel extends Routable { Map get preContained => _preContained; /// Returns the [flatten]ed version of this router in production. - Router get optimizedRouter => _flattened ?? this; + Router get optimizedRouter => _flattened ?? this; /// Determines whether to allow HTTP request method overrides. bool allowMethodOverrides = true; @@ -67,10 +67,10 @@ class Angel extends Routable { /// All child application mounted on this instance. List get children => List.unmodifiable(_children); - final Map _controllers = {}; + final Map _controllers = {}; /// A set of [Controller] objects that have been loaded into the application. - Map get controllers => _controllers; + Map get controllers => _controllers; /// Now *deprecated*, in favor of [AngelEnv] and [angelEnv]. Use `app.environment.isProduction` /// instead. @@ -148,9 +148,9 @@ class Angel extends Routable { }; @override - Route addRoute( - String? method, String? path, RequestHandler? handler, - {Iterable? middleware}) { + Route addRoute( + String method, String path, RequestHandler handler, + {Iterable? middleware}) { middleware ??= []; if (_flattened != null) { logger?.warning( @@ -163,7 +163,7 @@ class Angel extends Routable { } @override - mount(String path, Router router) { + mount(String path, Router router) { if (_flattened != null) { logger?.warning( 'WARNING: You added mounted a child router ($path) on the router, after it had been optimized.'); @@ -182,12 +182,12 @@ class Angel extends Routable { /// Loads some base dependencies into the service container. void bootstrapContainer() { if (runtimeType != Angel) { - container!.registerSingleton(this); + container?.registerSingleton(this); } - container!.registerSingleton(this); - container!.registerSingleton(this); - container!.registerSingleton(this); + container?.registerSingleton(this); + container?.registerSingleton(this); + container?.registerSingleton(this); } /// Shuts down the server, and closes any open [StreamController]s. @@ -284,7 +284,8 @@ class Angel extends Routable { /// Attempts to find a property by the given name within this application. findProperty(key) { if (configuration.containsKey(key)) return configuration[key]; - return parent != null ? parent!.findProperty(key) : null; + + return parent != null ? parent?.findProperty(key) : null; } /// Runs several optimizations, *if* [angelEnv.isProduction] is `true`. @@ -308,7 +309,7 @@ class Angel extends Routable { [Container? container]) { return Future.sync(() { if (_preContained.containsKey(handler)) { - return handleContained(handler, _preContained[handler], container)( + return handleContained(handler, _preContained[handler]!, container)( req, res); } @@ -319,7 +320,7 @@ class Angel extends Routable { /// Runs with DI, and *always* reflects. Prefer [runContained]. Future runReflected(Function handler, RequestContext req, ResponseContext res, [Container? container]) { - container ??= req.container ?? res.app?.container; + container ??= req.container ?? res.app!.container; var h = handleContained( handler, _preContained[handler] = preInject(handler, container!.reflector), diff --git a/packages/framework/lib/src/core/service.dart b/packages/framework/lib/src/core/service.dart index 16ea434d..ce225a10 100644 --- a/packages/framework/lib/src/core/service.dart +++ b/packages/framework/lib/src/core/service.dart @@ -75,7 +75,7 @@ class Service extends Routable { /// An optional [readData] function can be passed to handle non-map/non-json bodies. Service( - {FutureOr Function(RequestContext, ResponseContext?)? readData}) { + {FutureOr Function(RequestContext, ResponseContext)? readData}) { _readData = readData; _readData ??= (req, res) { @@ -84,15 +84,15 @@ class Service extends Routable { message: 'Invalid request body. Expected $Data; found ${req.bodyAsObject} instead.'); } else { - return req.bodyAsObject as Data?; + return req.bodyAsObject as Data; } }; } - FutureOr? Function(RequestContext, ResponseContext?)? _readData; + FutureOr Function(RequestContext, ResponseContext)? _readData; /// A [Function] that reads the request body and converts it into [Data]. - FutureOr? Function(RequestContext, ResponseContext?)? get readData => + FutureOr Function(RequestContext, ResponseContext)? get readData => _readData; /// Retrieves the first object from the result of calling [index] with the given [params]. @@ -108,14 +108,10 @@ class Service extends Routable { [Map? params, String errorMessage = 'No record was found matching the given query.']) { return index(params).then((result) { - if (result == null) { + if (result.isEmpty) { throw AngelHttpException.notFound(message: errorMessage); } else { - if (result.isEmpty) { - throw AngelHttpException.notFound(message: errorMessage); - } else { - return result.first; - } + return result.first; } }); } @@ -126,7 +122,7 @@ class Service extends Routable { } /// Retrieves the desired resource. - Future read(Id? id, [Map? params]) { + Future read(Id id, [Map? params]) { throw AngelHttpException.methodNotAllowed(); } @@ -144,17 +140,17 @@ class Service extends Routable { } /// Modifies a resource. - Future modify(Id? id, Data data, [Map? params]) { + Future modify(Id id, Data data, [Map? params]) { throw AngelHttpException.methodNotAllowed(); } /// Overwrites a resource. - Future update(Id? id, Data data, [Map? params]) { + Future update(Id id, Data data, [Map? params]) { throw AngelHttpException.methodNotAllowed(); } /// Removes the given resource. - Future remove(Id? id, [Map? params]) { + Future remove(Id id, [Map? params]) { throw AngelHttpException.methodNotAllowed(); } @@ -170,8 +166,7 @@ class Service extends Routable { }; return AnonymousService( - readData: readData as FutureOr Function( - RequestContext, ResponseContext?)?, + readData: readData, index: ([params]) { return index(params).then((it) => it.map(encoder).toList()); }, @@ -198,9 +193,9 @@ class Service extends Routable { /// The single type argument, [T], is used to determine how to parse the [id]. /// /// For example, `parseId` attempts to parse the value as a [bool]. - static T? parseId(id) { + static T parseId(id) { if (id == 'null' || id == null) { - return null; + return '' as T; } else if (T == String) { return id.toString() as T; } else if (T == int) { @@ -351,7 +346,7 @@ class Service extends Routable { getAnnotation(service.remove, app!.container!.reflector); delete('/', (req, res) { return this.remove( - null, + '' as Id, mergeMap([ {'query': req.queryParameters}, restProvider, diff --git a/packages/framework/lib/src/http/angel_http.dart b/packages/framework/lib/src/http/angel_http.dart index dcb8ef34..7371d589 100644 --- a/packages/framework/lib/src/http/angel_http.dart +++ b/packages/framework/lib/src/http/angel_http.dart @@ -20,11 +20,10 @@ class AngelHttp extends Driver { @override Uri get uri { - if (server == null) { - throw ArgumentError("[AngelHttp] Server instance not intialised"); - } - return Uri( - scheme: 'http', host: server!.address.address, port: server!.port); + //if (server == null) { + // throw ArgumentError("[AngelHttp] Server instance not intialised"); + //} + return Uri(scheme: 'http', host: server.address.address, port: server.port); } AngelHttp._(Angel app, @@ -70,10 +69,10 @@ class AngelHttp extends Driver diff --git a/packages/framework/lib/src/http/http_request_context.dart b/packages/framework/lib/src/http/http_request_context.dart index ec1afc99..e5227411 100644 --- a/packages/framework/lib/src/http/http_request_context.dart +++ b/packages/framework/lib/src/http/http_request_context.dart @@ -9,15 +9,16 @@ import '../core/core.dart'; /// An implementation of [RequestContext] that wraps a [HttpRequest]. class HttpRequestContext extends RequestContext { Container? _container; - MediaType? _contentType; + MediaType _contentType = MediaType("text", "html"); HttpRequest? _io; - String? _override, _path; + String? _override; + String _path = ''; @override Container? get container => _container; @override - MediaType? get contentType { + MediaType get contentType { return _contentType; } @@ -32,8 +33,8 @@ class HttpRequestContext extends RequestContext { } @override - String? get hostname { - return rawRequest!.headers.value('host'); + String get hostname { + return rawRequest?.headers.value('host') ?? "localhost"; } /// The underlying [HttpRequest] instance underneath this context. @@ -53,7 +54,7 @@ class HttpRequestContext extends RequestContext { } @override - String? get path { + String get path { return _path; } @@ -88,7 +89,7 @@ class HttpRequestContext extends RequestContext { ctx.app = app; ctx._contentType = request.headers.contentType == null - ? null + ? MediaType("text", "html") : MediaType.parse(request.headers.contentType.toString()); ctx._override = override; @@ -128,9 +129,10 @@ class HttpRequestContext extends RequestContext { @override Future close() { - _contentType = null; + //_contentType = null; _io = null; - _override = _path = null; + _override = null; + //_path = null; return super.close(); } } diff --git a/packages/framework/lib/src/http/http_response_context.dart b/packages/framework/lib/src/http/http_response_context.dart index 6cef2527..077fe956 100644 --- a/packages/framework/lib/src/http/http_response_context.dart +++ b/packages/framework/lib/src/http/http_response_context.dart @@ -57,8 +57,8 @@ class HttpResponseContext extends ResponseContext { Iterable? __allowedEncodings; Iterable? get _allowedEncodings { - return __allowedEncodings ??= correspondingRequest!.headers! - .value('accept-encoding') + return __allowedEncodings ??= correspondingRequest?.headers + ?.value('accept-encoding') ?.split(',') .map((s) => s.trim()) .where((s) => s.isNotEmpty) diff --git a/packages/framework/lib/src/http2/angel_http2.dart b/packages/framework/lib/src/http2/angel_http2.dart index edd7f515..e73dfa56 100644 --- a/packages/framework/lib/src/http2/angel_http2.dart +++ b/packages/framework/lib/src/http2/angel_http2.dart @@ -45,7 +45,9 @@ class AngelHttp2 extends Driver generateServer([address, int? port]) async { - SecureServerSocket s = await serverGenerator(address ?? '127.0.0.1', port ?? 0); + SecureServerSocket s = + await serverGenerator(address ?? '127.0.0.1', port ?? 0); return _artificial = _AngelHttp2ServerSocket(s, this); } @override Future close() async { await _artificial!.close(); - await _http?.close(); + await _http.close(); return await super.close(); } @@ -98,7 +101,7 @@ class AngelHttp2 extends Driver createRequestContext( Socket request, ServerTransportStream response) { - return Http2RequestContext.from(response, request, app!, _sessions, _uuid); + return Http2RequestContext.from(response, request, app, _sessions, _uuid); } @override @@ -106,7 +109,7 @@ class AngelHttp2 extends Driver Uri( scheme: 'https', - host: server!.address.address, - port: server!.port != 443 ? server!.port : null); + host: server.address.address, + port: server.port != 443 ? server.port : null); @override void writeStringToResponse(ServerTransportStream response, String value) { @@ -208,7 +211,7 @@ class _AngelHttp2ServerSocket extends Stream }, onDone: _ctrl.close, onError: (e, st) { - driver.app!.logger!.warning( + driver.app.logger!.warning( 'HTTP/2 incoming connection failure: ', e, st as StackTrace); }, ); diff --git a/packages/framework/lib/src/http2/http2_request_context.dart b/packages/framework/lib/src/http2/http2_request_context.dart index 8bbb7678..6aab4771 100644 --- a/packages/framework/lib/src/http2/http2_request_context.dart +++ b/packages/framework/lib/src/http2/http2_request_context.dart @@ -14,13 +14,13 @@ final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)'); class Http2RequestContext extends RequestContext { final StreamController> _body = StreamController(); final Container container; - List? _cookies; + List _cookies = []; HttpHeaders? _headers; String? _method, _override, _path; - HttpSession? _session; late Socket _socket; ServerTransportStream? _stream; Uri? _uri; + HttpSession? _session; Http2RequestContext._(this.container); @@ -124,8 +124,7 @@ class Http2RequestContext extends RequestContext { }, cancelOnError: true, onError: c.completeError); // Apply session - var dartSessId = - cookies.firstWhereOrNull((c) => c.name == 'DARTSESSID'); + var dartSessId = cookies.firstWhereOrNull((c) => c.name == 'DARTSESSID'); if (dartSessId == null) { dartSessId = Cookie('DARTSESSID', uuid.v4()); @@ -140,7 +139,7 @@ class Http2RequestContext extends RequestContext { } @override - List? get cookies => _cookies; + List get cookies => _cookies; /// The underlying HTTP/2 [ServerTransportStream]. ServerTransportStream? get stream => _stream; @@ -157,22 +156,22 @@ class Http2RequestContext extends RequestContext { InternetAddress get remoteAddress => _socket.remoteAddress; @override - String? get path { - return _path; + String get path { + return _path ?? ''; } @override - String? get originalMethod { - return _method; + String get originalMethod { + return _method ?? 'GET'; } @override - String? get method { - return _override ?? _method; + String get method { + return _override ?? _method ?? 'GET'; } @override - String? get hostname => _headers!.value('host'); + String get hostname => _headers?.value('host') ?? 'localhost'; @override HttpHeaders? get headers => _headers; diff --git a/packages/framework/lib/src/http2/http2_response_context.dart b/packages/framework/lib/src/http2/http2_response_context.dart index a5911784..45d755ec 100644 --- a/packages/framework/lib/src/http2/http2_response_context.dart +++ b/packages/framework/lib/src/http2/http2_response_context.dart @@ -116,8 +116,8 @@ class Http2ResponseContext extends ResponseContext { Iterable? __allowedEncodings; Iterable? get _allowedEncodings { - return __allowedEncodings ??= correspondingRequest!.headers! - .value('accept-encoding') + return __allowedEncodings ??= correspondingRequest?.headers + ?.value('accept-encoding') ?.split(',') .map((s) => s.trim()) .where((s) => s.isNotEmpty) diff --git a/packages/framework/test/find_one_test.dart b/packages/framework/test/find_one_test.dart index 5a576fd4..2e23d421 100644 --- a/packages/framework/test/find_one_test.dart +++ b/packages/framework/test/find_one_test.dart @@ -6,10 +6,12 @@ void main() { var throwsAnAngelHttpException = throwsA(const IsInstanceOf()); + /* test('throw 404 on null', () { var service = AnonymousService(index: ([p]) => null); expect(() => service.findOne(), throwsAnAngelHttpException); }); + */ test('throw 404 on empty iterable', () { var service = AnonymousService(index: ([p]) => []); diff --git a/packages/framework/test/http2/adapter_test.dart b/packages/framework/test/http2/adapter_test.dart index 3ffcb541..d0436ff8 100644 --- a/packages/framework/test/http2/adapter_test.dart +++ b/packages/framework/test/http2/adapter_test.dart @@ -70,7 +70,7 @@ void main() { UploadedFile file = files.firstWhereOrNull((f) => f.name == 'file')!; return [ await file.data.map((l) => l.length).reduce((a, b) => a + b), - file.contentType!.mimeType, + file.contentType.mimeType, body ]; }); @@ -188,7 +188,7 @@ void main() { test('server push', () async { var socket = await SecureSocket.connect( serverRoot.host, - serverRoot.port ?? 443, + serverRoot.port, onBadCertificate: (_) => true, supportedProtocols: ['h2'], ); diff --git a/packages/framework/test/http2/http2_client.dart b/packages/framework/test/http2/http2_client.dart index a4520ba8..2903fe3c 100644 --- a/packages/framework/test/http2/http2_client.dart +++ b/packages/framework/test/http2/http2_client.dart @@ -11,7 +11,7 @@ class Http2Client extends BaseClient { // Connect a socket var socket = await SecureSocket.connect( request.url.host, - request.url.port ?? 443, + request.url.port, onBadCertificate: (_) => true, supportedProtocols: ['h2'], ); diff --git a/packages/framework/test/routing_test.dart b/packages/framework/test/routing_test.dart index 36426968..7db682d0 100644 --- a/packages/framework/test/routing_test.dart +++ b/packages/framework/test/routing_test.dart @@ -179,6 +179,7 @@ main() { expect(response.body, equals('"MJ"')); }); + /* TODO: Revisit this later test('Can name routes', () { Route foo = app.get('/framework/:id', null)..name = 'frm'; print('Foo: $foo'); @@ -186,6 +187,7 @@ main() { print(uri); expect(uri, equals('framework/angel')); }); + */ test('Redirect to named routes', () async { var response = await client.get(Uri.parse('$url/named')); diff --git a/packages/route/lib/src/router.dart b/packages/route/lib/src/router.dart index 24a8404c..6895f0b9 100644 --- a/packages/route/lib/src/router.dart +++ b/packages/route/lib/src/router.dart @@ -73,7 +73,6 @@ class Router { // Check if any mounted routers can match this final handlers = [handler]; - //middleware = []; middleware ??= []; handlers.insertAll(0, middleware); @@ -373,50 +372,42 @@ class Router { } /// Adds a route that responds to any request matching the given path. - Route all(String path, T handler, - {Iterable middleware = const Iterable.empty()}) { + Route all(String path, T handler, {Iterable? middleware}) { return addRoute('*', path, handler, middleware: middleware); } /// Adds a route that responds to a DELETE request. - Route delete(String path, T handler, - {Iterable middleware = const Iterable.empty()}) { + Route delete(String path, T handler, {Iterable? middleware}) { return addRoute('DELETE', path, handler, middleware: middleware); } /// Adds a route that responds to a GET request. - Route get(String path, T handler, - {Iterable middleware = const Iterable.empty()}) { + Route get(String path, T handler, {Iterable? middleware}) { return addRoute('GET', path, handler, middleware: middleware); } /// Adds a route that responds to a HEAD request. - Route head(String path, T handler, - {Iterable middleware = const Iterable.empty()}) { + Route head(String path, T handler, {Iterable? middleware}) { return addRoute('HEAD', path, handler, middleware: middleware); } /// Adds a route that responds to a OPTIONS request. - Route options(String path, T handler, - {Iterable middleware = const Iterable.empty()}) { + Route options(String path, T handler, {Iterable? middleware}) { return addRoute('OPTIONS', path, handler, middleware: middleware); } /// Adds a route that responds to a POST request. - Route post(String path, T handler, - {Iterable middleware = const Iterable.empty()}) { + Route post(String path, T handler, {Iterable? middleware}) { return addRoute('POST', path, handler, middleware: middleware); } /// Adds a route that responds to a PATCH request. - Route patch(String path, T handler, - {Iterable middleware = const Iterable.empty()}) { + Route patch(String path, T handler, {Iterable? middleware}) { return addRoute('PATCH', path, handler, middleware: middleware); } /// Adds a route that responds to a PUT request. - Route put(String path, T handler, - {Iterable middleware = const Iterable.empty()}) { + Route put(String path, T handler, {Iterable? middleware}) { return addRoute('PUT', path, handler, middleware: middleware); } } @@ -443,7 +434,8 @@ class _ChainedRouter extends Router { @override SymlinkRoute group(String path, void Function(Router router) callback, - {Iterable middleware = const Iterable.empty(), String name = ''}) { + {Iterable? middleware, String name = ''}) { + middleware ??= []; final router = _ChainedRouter(_root, [..._handlers, ...middleware]); callback(router); return mount(path, router)..name = name; @@ -452,8 +444,8 @@ class _ChainedRouter extends Router { @override Future> groupAsync( String path, FutureOr Function(Router router) callback, - {Iterable middleware = const Iterable.empty(), - String name = ''}) async { + {Iterable? middleware, String name = ''}) async { + middleware ??= []; final router = _ChainedRouter(_root, [..._handlers, ...middleware]); await callback(router); return mount(path, router)..name = name; From 526c7618460bbe3b607397675ffc50245e6feda8 Mon Sep 17 00:00:00 2001 From: thomashii Date: Fri, 9 Apr 2021 21:38:31 +0800 Subject: [PATCH 029/171] Changed statusCode and message to be not nullable --- .../http_exception/lib/angel_http_exception.dart | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/http_exception/lib/angel_http_exception.dart b/packages/http_exception/lib/angel_http_exception.dart index b38d854e..8ccd34dc 100644 --- a/packages/http_exception/lib/angel_http_exception.dart +++ b/packages/http_exception/lib/angel_http_exception.dart @@ -15,22 +15,20 @@ class AngelHttpException implements Exception { final List errors = []; /// The cause of this exception. - String? message; + String message; /// The [StackTrace] associated with this error. StackTrace? stackTrace; /// An HTTP status code this exception will throw. - int? statusCode; + int statusCode; AngelHttpException(this.error, {this.message = '500 Internal Server Error', this.stackTrace, this.statusCode = 500, List errors = const []}) { - if (errors != null) { - this.errors.addAll(errors); - } + this.errors.addAll(errors); } Map toJson() { @@ -52,8 +50,8 @@ class AngelHttpException implements Exception { factory AngelHttpException.fromMap(Map data) { return AngelHttpException( null, - statusCode: (data['status_code'] ?? data['statusCode']) as int?, - message: data['message']?.toString(), + statusCode: (data['status_code'] ?? data['statusCode'] ?? 500) as int, + message: data['message']?.toString() ?? 'Internal Server Error', errors: data['errors'] is Iterable ? ((data['errors'] as Iterable).map((x) => x.toString()).toList()) : [], From d211fc2c3dee84ccabfa515885573b0b42d8893e Mon Sep 17 00:00:00 2001 From: thomashii Date: Sat, 10 Apr 2021 19:23:57 +0800 Subject: [PATCH 030/171] Migrated angel_configuration --- CHANGELOG.md | 22 +++++------ packages/auth/lib/src/auth_token.dart | 2 +- packages/auth/lib/src/plugin.dart | 12 +++--- packages/auth/test/callback_test.dart | 26 ++++++------- .../lib/angel_configuration.dart | 22 +++++------ packages/configuration/pubspec.yaml | 12 ++++-- packages/framework/.vscode/settings.json | 3 ++ packages/framework/example/handle_error.dart | 2 +- packages/framework/example/hostname.dart | 2 +- packages/framework/example/json.dart | 2 +- packages/framework/lib/src/core/driver.dart | 38 +++++++++++++++---- .../framework/lib/src/core/injection.dart | 20 +++++----- .../lib/src/core/request_context.dart | 2 +- .../lib/src/core/response_context.dart | 15 ++++++-- packages/framework/lib/src/core/server.dart | 2 +- packages/framework/lib/src/core/service.dart | 5 ++- .../framework/lib/src/http/angel_http.dart | 31 +++++++-------- .../lib/src/http/http_request_context.dart | 4 +- .../framework/lib/src/http2/angel_http2.dart | 6 +-- packages/framework/test/accepts_test.dart | 2 +- .../framework/test/encoders_buffer_test.dart | 4 +- packages/framework/test/services_test.dart | 1 + packages/http_exception/.vscode/settings.json | 5 +++ packages/http_exception/example/main.dart | 2 +- packages/route/.vscode/settings.json | 5 +++ 25 files changed, 145 insertions(+), 102 deletions(-) create mode 100644 packages/http_exception/.vscode/settings.json create mode 100644 packages/route/.vscode/settings.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 06dbba6a..daa66f43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,17 @@ # 4.0.0 (NNBD) * Changed Dart SDK requirements for all packages to ">=2.12.0 <3.0.0" to support NNBD. -* Updated pretty_logging to 3.0.0 -* Updated angel_http_exception to 3.0.0 +* Updated pretty_logging to 3.0.0 (0/0 tests) +* Updated angel_http_exception to 3.0.0 (0/0 tests) * Moved angel_cli to https://github.com/dukefirehawk/cli -* Added code_buffer and updated to 2.0.0 -* Added combinator and updated to 2.0.0 -* Updated angel_route to 5.0.0 -* Updated angel_model to 3.0.0 -* Updated angel_container to 3.0.0 -* Added merge_map and updated to 2.0.0 -* Added mock_request and updated to 2.0.0 -* Updated angel_framework to 4.0.0 (Revisit TODO, Fix failed tests) -* Updated angel_auth to 4.0.0 (In progress) +* Added code_buffer and updated to 2.0.0 (16/16 tests) +* Added combinator and updated to 2.0.0 (16/16 tests) +* Updated angel_route to 5.0.0 (35/35 tests passed) +* Updated angel_model to 3.0.0 (0/0 tests) +* Updated angel_container to 3.0.0 (55/55 tests passed) +* Added merge_map and updated to 2.0.0 (6/6 tests passed) +* Added mock_request and updated to 2.0.0 (0/0 tests) +* Updated angel_framework to 4.0.0 (146/149 tests passed) +* Updated angel_auth to 4.0.0 (22/32 test passed) * Updated angel_configuration to 4.0.0 (In progress) # 3.0.0 (Non NNBD) diff --git a/packages/auth/lib/src/auth_token.dart b/packages/auth/lib/src/auth_token.dart index 053b22b5..8c6b9c9b 100644 --- a/packages/auth/lib/src/auth_token.dart +++ b/packages/auth/lib/src/auth_token.dart @@ -41,7 +41,7 @@ class AuthToken { DateTime? issuedAt, Map payload = const {}}) { this.issuedAt = issuedAt ?? DateTime.now(); - this.payload.addAll(payload?.keys?.fold( + this.payload.addAll(payload.keys.fold( {}, ((out, k) => out..[k.toString()] = payload[k]) as Map? Function( diff --git a/packages/auth/lib/src/plugin.dart b/packages/auth/lib/src/plugin.dart index 57878eca..f09da924 100644 --- a/packages/auth/lib/src/plugin.dart +++ b/packages/auth/lib/src/plugin.dart @@ -217,16 +217,16 @@ class AngelAuth { /// Retrieves a JWT from a request, if any was sent at all. String? getJwt(RequestContext req) { - if (req.headers!.value("Authorization") != null) { - final authHeader = req.headers!.value("Authorization")!; + if (req.headers!.value('Authorization') != null) { + final authHeader = req.headers!.value('Authorization')!; // Allow Basic auth to fall through if (_rgxBearer.hasMatch(authHeader)) { - return authHeader.replaceAll(_rgxBearer, "").trim(); + return authHeader.replaceAll(_rgxBearer, '').trim(); } } else if (allowCookie && - req.cookies!.any((cookie) => cookie.name == "token")) { - return req.cookies!.firstWhere((cookie) => cookie.name == "token").value; + req.cookies.any((cookie) => cookie.name == 'token')) { + return req.cookies.firstWhere((cookie) => cookie.name == 'token').value; } else if (allowTokenInQuery && req.uri!.queryParameters['token'] is String) { return req.uri!.queryParameters['token']?.toString(); @@ -266,7 +266,7 @@ class AngelAuth { var jwt = getJwt(req); if (jwt == null) { - var body = await req.parseBody().then((_) => req.bodyAsMap!); + var body = await req.parseBody().then((_) => req.bodyAsMap); jwt = body['token']?.toString(); } if (jwt == null) { diff --git a/packages/auth/test/callback_test.dart b/packages/auth/test/callback_test.dart index 25b86d3e..1f1043da 100644 --- a/packages/auth/test/callback_test.dart +++ b/packages/auth/test/callback_test.dart @@ -34,7 +34,7 @@ class User extends Model { } void main() { - Angel? app; + late Angel app; late AngelHttp angelHttp; AngelAuth auth; http.Client? client; @@ -45,15 +45,15 @@ void main() { hierarchicalLoggingEnabled = true; app = Angel(); angelHttp = AngelHttp(app); - app!.use('/users', MapService()); + app.use('/users', MapService()); - var oldErrorHandler = app!.errorHandler; - app!.errorHandler = (e, req, res) { - app!.logger!.severe(e.message, e, e.stackTrace ?? StackTrace.current); + var oldErrorHandler = app.errorHandler; + app.errorHandler = (e, req, res) { + app.logger!.severe(e.message, e, e.stackTrace ?? StackTrace.current); return oldErrorHandler(e, req, res); }; - app!.logger = Logger('angel_auth') + app.logger = Logger('angel_auth') ..level = Level.FINEST ..onRecord.listen((rec) { print(rec); @@ -67,19 +67,19 @@ void main() { } }); - await app! + await app .findService('users')! .create({'username': 'jdoe1', 'password': 'password'}); auth = AngelAuth(); auth.serializer = (u) => u!.id; auth.deserializer = - (id) async => await app!.findService('users')!.read(id) as User; + (id) async => await app.findService('users')!.read(id) as User; - await app!.configure(auth.configureServer); + await app.configure(auth.configureServer); auth.strategies['local'] = LocalAuthStrategy((username, password) async { - var users = await app! + var users = await app .findService('users')! .index() .then((it) => it.map((m) => User.parse(m as Map)).toList()); @@ -90,7 +90,7 @@ void main() { return Future.value(result); }); - app!.post( + app.post( '/login', auth.authenticate('local', AngelAuthOptions(callback: (req, res, token) { @@ -99,7 +99,7 @@ void main() { ..close(); }))); - app!.chain([ + app.chain([ (req, res) { if (!req.container!.has()) { req.container!.registerSingleton( @@ -120,7 +120,7 @@ void main() { tearDown(() async { client!.close(); await angelHttp.close(); - app = null; + //app = null; client = null; url = null; }); diff --git a/packages/configuration/lib/angel_configuration.dart b/packages/configuration/lib/angel_configuration.dart index 60b9f72a..d1e62f3e 100644 --- a/packages/configuration/lib/angel_configuration.dart +++ b/packages/configuration/lib/angel_configuration.dart @@ -21,7 +21,7 @@ Future _loadYamlFile(Map map, File yamlFile, Map env, var out = {}; - var configMap = Map.of(config as Map); + var configMap = Map.of(config); // Check for _include if (configMap.containsKey('_include')) { @@ -44,8 +44,8 @@ Future _loadYamlFile(Map map, File yamlFile, Map env, } } - for (String key in configMap.keys) { - out[key] = _applyEnv(configMap[key], env ?? {}, warn); + for (var key in configMap.keys as Iterable) { + out[key] = _applyEnv(configMap[key], env, warn); } map.addAll(mergeMap( @@ -58,7 +58,7 @@ Future _loadYamlFile(Map map, File yamlFile, Map env, } } -Object _applyEnv( +Object? _applyEnv( var v, Map env, void Function(String msg) warn) { if (v is String) { if (v.startsWith(r'$') && v.length > 1) { @@ -74,10 +74,10 @@ Object _applyEnv( return v; } } else if (v is Iterable) { - return v.map((x) => _applyEnv(x, env ?? {}, warn)).toList(); + return v.map((x) => _applyEnv(x, env, warn)).toList(); } else if (v is Map) { return v.keys - .fold({}, (out, k) => out..[k] = _applyEnv(v[k], env ?? {}, warn)); + .fold({}, (out, k) => out..[k] = _applyEnv(v[k], env, warn)); } else { return v; } @@ -88,9 +88,9 @@ Object _applyEnv( /// You can override [onWarning]; otherwise, configuration errors will throw. Future loadStandaloneConfiguration(FileSystem fileSystem, {String directoryPath = './config', - String overrideEnvironmentName, - String envPath, - void Function(String message) onWarning}) async { + String? overrideEnvironmentName, + String? envPath, + void Function(String message)? onWarning}) async { var sourceDirectory = fileSystem.directory(directoryPath); var env = dotenv.env; var envFile = sourceDirectory.childFile(envPath ?? '.env'); @@ -127,8 +127,8 @@ Future loadStandaloneConfiguration(FileSystem fileSystem, /// You can also specify a custom [envPath] to load system configuration from. AngelConfigurer configuration(FileSystem fileSystem, {String directoryPath = './config', - String overrideEnvironmentName, - String envPath}) { + String? overrideEnvironmentName, + String? envPath}) { return (Angel app) async { var config = await loadStandaloneConfiguration( fileSystem, diff --git a/packages/configuration/pubspec.yaml b/packages/configuration/pubspec.yaml index 0108b57e..5a3459a9 100644 --- a/packages/configuration/pubspec.yaml +++ b/packages/configuration/pubspec.yaml @@ -5,20 +5,24 @@ author: Tobe O homepage: https://github.com/angel-dart/angel_configuration publish_to: none environment: - sdk: ">=2.10.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' 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 dotenv: ^3.0.0-nullsafety.0 # file: ^5.0.0 - merge_map: ^1.0.0 + merge_map: + git: + url: https://github.com/dukefirehawk/angel.git + ref: sdk-2.12.x_nnbd + path: packages/merge_map yaml: ^3.1.0 dev_dependencies: io: ^1.0.0 # logging: ^0.11.0 - pedantic: ^1.0.0 + pedantic: ^1.11.0 # pretty_logging: ^1.0.0 test: ^1.15.7 diff --git a/packages/framework/.vscode/settings.json b/packages/framework/.vscode/settings.json index 20af2f68..b672a778 100644 --- a/packages/framework/.vscode/settings.json +++ b/packages/framework/.vscode/settings.json @@ -1,3 +1,6 @@ // Place your settings in this file to overwrite default and user settings. { + "files.watcherExclude": { + "**/target": true + } } \ No newline at end of file diff --git a/packages/framework/example/handle_error.dart b/packages/framework/example/handle_error.dart index e964092a..8e32af63 100644 --- a/packages/framework/example/handle_error.dart +++ b/packages/framework/example/handle_error.dart @@ -19,7 +19,7 @@ main() async { (req, res) => Future.error('Throwing just because I feel like!')); var http = AngelHttp(app); - HttpServer server = await http.startServer('127.0.0.1', 3000); + HttpServer? server = await http.startServer('127.0.0.1', 3000); var url = 'http://${server.address.address}:${server.port}'; print('Listening at $url'); } diff --git a/packages/framework/example/hostname.dart b/packages/framework/example/hostname.dart index 66003dd0..54271951 100644 --- a/packages/framework/example/hostname.dart +++ b/packages/framework/example/hostname.dart @@ -33,7 +33,7 @@ main() async { }); app.errorHandler = (e, req, res) { - print(e.message ?? e.error ?? e); + print(e.message); print(e.stackTrace); return e.toJson(); }; diff --git a/packages/framework/example/json.dart b/packages/framework/example/json.dart index 16ae5baf..5d499523 100644 --- a/packages/framework/example/json.dart +++ b/packages/framework/example/json.dart @@ -44,7 +44,7 @@ serverMain(_) async { }); app.errorHandler = (e, req, res) { - print(e.message ?? e.error ?? e); + print(e.message); print(e.stackTrace); }; diff --git a/packages/framework/lib/src/core/driver.dart b/packages/framework/lib/src/core/driver.dart index c52cc244..21b1dace 100644 --- a/packages/framework/lib/src/core/driver.dart +++ b/packages/framework/lib/src/core/driver.dart @@ -22,6 +22,10 @@ abstract class Driver< final bool useZone; bool _closed = false; late Server _server; + + // TODO: Ugly fix + bool isServerInitialised = false; + StreamSubscription? _sub; final log = Logger('Driver'); @@ -34,16 +38,27 @@ abstract class Driver< Uri get uri; /// The native server running this instance. - Server get server => _server; + Server? get server { + // TODO: Ugly fix + if (isServerInitialised) { + return _server; + } else { + return null; + } + } Future generateServer(address, int port) => serverGenerator(address, port); /// Starts, and returns the server. - Future startServer([address, int port = 5000]) { + Future startServer([address, int port = 0]) { var host = address ?? '127.0.0.1'; return generateServer(host, port).then((server) { _server = server; + + // TODO: Ugly fix + isServerInitialised = true; + return Future.wait(app.startupHooks.map(app.configure)).then((_) { app.optimizeForProduction(); _sub = server.listen((request) { @@ -62,16 +77,23 @@ abstract class Driver< } /// Shuts down the underlying server. - Future close() { + Future close() { if (_closed) { - return Future.value(_server); + //return Future.value(_server); + return Future.value(); } _closed = true; _sub?.cancel(); + + return app.close().then((_) => + Future.wait(app.shutdownHooks.map(app.configure)) + .then((_) => Future.value())); + /* return app.close().then((_) => Future.wait(app.shutdownHooks.map(app.configure)) .then((_) => Future.value(_server))); + */ } Future createRequestContext( @@ -81,7 +103,7 @@ abstract class Driver< Request request, Response response, [RequestContextType? correspondingRequest]); - void setHeader(Response response, String key, String? value); + void setHeader(Response response, String key, String value); void setContentLength(Response response, int length); @@ -174,7 +196,7 @@ abstract class Driver< if (app.logger != null) { var error = e.error ?? e; var trace = Trace.from(e.stackTrace ?? StackTrace.current).terse; - app.logger?.severe(e.message ?? e.toString(), error, trace); + app.logger?.severe(e.message, error, trace); } return handleAngelHttpException( @@ -206,7 +228,7 @@ abstract class Driver< } if (app.logger != null) { - app.logger?.severe(e.message ?? e.toString(), error, trace); + app.logger?.severe(e.message, error, trace); } return handleAngelHttpException( @@ -309,7 +331,7 @@ abstract class Driver< //if (res.isOpen) res.close(); for (var key in res.headers.keys) { - setHeader(response, key, res.headers[key]); + setHeader(response, key, res.headers[key] ?? ''); } setContentLength(response, res.buffer!.length); diff --git a/packages/framework/lib/src/core/injection.dart b/packages/framework/lib/src/core/injection.dart index 65698e5a..d5268a01 100644 --- a/packages/framework/lib/src/core/injection.dart +++ b/packages/framework/lib/src/core/injection.dart @@ -8,18 +8,16 @@ const List _primitiveTypes = [String, int, num, double, Null]; /// /// Calling [ioc] also auto-serializes the result of a [handler]. RequestHandler ioc(Function handler, {Iterable optional = const []}) { - InjectionRequest? injection; - RequestHandler? contained; - return (req, res) { - if (injection == null) { - if (req.app!.container != null) { - injection = preInject(handler, req.app!.container!.reflector); - if (injection != null) { - injection?.optional.addAll(optional); - contained = handleContained(handler, injection!); - } - } + RequestHandler? contained; + + if (req.app?.container != null) { + InjectionRequest injection = + preInject(handler, req.app!.container!.reflector); + //if (injection != null) { + injection.optional.addAll(optional); + contained = handleContained(handler, injection); + //} } return req.app!.executeHandler(contained, req, res); diff --git a/packages/framework/lib/src/core/request_context.dart b/packages/framework/lib/src/core/request_context.dart index 9801bf21..6ad8dc67 100644 --- a/packages/framework/lib/src/core/request_context.dart +++ b/packages/framework/lib/src/core/request_context.dart @@ -43,7 +43,7 @@ abstract class RequestContext { Map _bodyFields = {}; List _bodyList = []; List _uploadedFiles = []; - MediaType _contentType = MediaType("text", "html"); + MediaType _contentType = MediaType("text", "plain"); /// The underlying [RawRequest] provided by the driver. RawRequest get rawRequest; diff --git a/packages/framework/lib/src/core/response_context.dart b/packages/framework/lib/src/core/response_context.dart index bd635e82..c993def9 100644 --- a/packages/framework/lib/src/core/response_context.dart +++ b/packages/framework/lib/src/core/response_context.dart @@ -9,6 +9,7 @@ import 'dart:typed_data'; import 'package:angel_route/angel_route.dart'; import 'package:file/file.dart'; import 'package:http_parser/http_parser.dart'; +import 'package:logging/logging.dart'; import 'package:mime/mime.dart'; import 'controller.dart'; @@ -26,6 +27,8 @@ abstract class ResponseContext 'server': 'angel', }); + final log = Logger('ResponseContext'); + Completer? _done; int _statusCode = 200; @@ -76,11 +79,11 @@ abstract class ResponseContext /// This response's status code. int get statusCode => _statusCode; - set statusCode(int? value) { + set statusCode(int value) { if (!isOpen) { throw closed(); } else { - _statusCode = value ?? 200; + _statusCode = value; // ?? 200; } } @@ -202,7 +205,7 @@ abstract class ResponseContext /// based on the provided params. /// /// See [Router]#navigate for more. :) - Future redirect(url, {bool absolute = true, int? code = 302}) { + Future redirect(url, {bool absolute = true, int? code}) { if (!isOpen) throw closed(); headers ..['content-type'] = 'text/html' @@ -344,7 +347,11 @@ abstract class ResponseContext if (_done?.isCompleted == false) { _done!.completeError(error, stackTrace); } else if (_done == null) { - Zone.current.handleUncaughtError(error, stackTrace!); + if (stackTrace != null) { + Zone.current.handleUncaughtError(error, stackTrace); + } else { + log.warning("[ResponseContext] stackTrace is null"); + } } } diff --git a/packages/framework/lib/src/core/server.dart b/packages/framework/lib/src/core/server.dart index 969d966c..96b83916 100644 --- a/packages/framework/lib/src/core/server.dart +++ b/packages/framework/lib/src/core/server.dart @@ -135,7 +135,7 @@ class Angel extends Routable { } res.contentType = MediaType('text', 'html', {'charset': 'utf8'}); - res.statusCode = e.statusCode; + res.statusCode = e.statusCode; // ?? 200; res.write("${e.message}"); res.write("

${e.message}