diff --git a/example/controller.dart b/example/controller.dart new file mode 100644 index 00000000..1e0da928 --- /dev/null +++ b/example/controller.dart @@ -0,0 +1,59 @@ +import 'package:angel_container/mirrors.dart'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_framework/http.dart'; +import 'package:logging/logging.dart'; + +main() async { + // Logging set up/boilerplate + Logger.root.onRecord.listen(print); + + // Create our server. + var app = Angel(logger: Logger('angel'), reflector: MirrorsReflector()); + var http = AngelHttp(app); + + await app.mountController(); + + // Simple fallback to throw a 404 on unknown paths. + app.fallback((req, res) { + throw AngelHttpException.notFound( + message: 'Unknown path: "${req.uri.path}"', + ); + }); + + app.errorHandler = (e, req, res) => e.toJson(); + + await http.startServer('127.0.0.1', 3000); + print('Listening at ${http.uri}'); + app.dumpTree(); +} + +class ArtistsController extends Controller { + List index() { + return ['Elvis', 'Stevie', 'Van Gogh']; + } + + String getById(int id, RequestContext req) { + return 'You fetched ID: $id from IP: ${req.ip}'; + } + + @Expose.post + 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 it (it will be serialized to JSON). + return artist; + } +} + +class Artist { + final String name; + + Artist({this.name}); + + Map toJson() { + return {'name': name}; + } +} diff --git a/lib/src/core/controller.dart b/lib/src/core/controller.dart index 9dd11cd5..4f3d4a5f 100644 --- a/lib/src/core/controller.dart +++ b/lib/src/core/controller.dart @@ -127,9 +127,31 @@ class Controller { // If there is no path, reverse-engineer one. var path = exposeDecl.path; + var httpMethod = exposeDecl.method ?? 'GET'; if (path == null) { var parts = []; - parts.add(ReCase(method.name).snakeCase.replaceAll(_multiScore, '_')); + var methodMatch = _methods.firstMatch(method.name); + + if (methodMatch != null) { + var rest = method.name.replaceAll(_methods, ''); + var restPath = ReCase(rest.isEmpty ? 'index' : rest) + .snakeCase + .replaceAll(_multiScore, '_'); + httpMethod = methodMatch[1].toUpperCase(); + + if (['index', 'by_id'].contains(restPath)) { + parts.add('/'); + } else { + parts.add(restPath); + } + } else { + if (method.name == 'index') { + parts.add('/'); + } else { + parts.add( + ReCase(method.name).snakeCase.replaceAll(_multiScore, '_')); + } + } // Try to infer String, int, or double. for (var p in injection.required) { @@ -149,8 +171,8 @@ class Controller { path = parts.join('/'); } - routeMappings[name] = routable.addRoute(exposeDecl.method, - exposeDecl.path, handleContained(reflectedMethod, injection), + routeMappings[name] = routable.addRoute( + httpMethod, path, handleContained(reflectedMethod, injection), middleware: middleware); } }; @@ -159,6 +181,7 @@ class Controller { /// Used to add additional routes to the router from within a [Controller]. void configureRoutes(Routable routable) {} + static final RegExp _methods = RegExp(r'^(get|post|patch|delete)'); static final RegExp _multiScore = RegExp(r'__+'); /// Finds the [Expose] declaration for this class.