of static method
The plan for fn on route. Throws a StateError when a
parameter type is not known.
Implementation
static ControllerPlan of(Function fn, RouteData route) {
String where = '${route.method} ${route.path}';
List<String> types = ControllerTypes.parameters(fn, where);
IocContainer ioc = Dox().ioc;
Map<String, RouteBinding> explicit = Route().bindings;
List<RouteParamBinder> binders = <RouteParamBinder>[
for (String param in route.paramNames)
if (explicit.containsKey(param))
RouteParamBinder(param, explicit[param]!.resolve),
];
// a parameter of a bound type takes its param, the others take the
// rest of the params in order
Map<int, String> boundArgs = <int, String>{};
for (int i = 0; i < types.length; i++) {
String name = _withoutNull(types[i]);
for (RouteParamBinder binder in binders) {
String param = binder.param;
if (explicit[param]!.type == name && !boundArgs.containsValue(param)) {
boundArgs[i] = param;
break;
}
}
}
// a parameter of a @DoxModel type takes the param with its name
// (`{user}` for `User`), else the next param in order
Map<int, RouteBinder<Object>> models = <int, RouteBinder<Object>>{};
for (int i = 0; i < types.length; i++) {
if (boundArgs.containsKey(i)) continue;
String name = _withoutNull(types[i]);
if (_pathTypes.containsKey(name) || name == 'dynamic') continue;
RouteBinder<Object>? find = ioc.modelBinderNamed(name);
if (find == null) continue;
models[i] = find;
String param = name[0].toLowerCase() + name.substring(1);
if (route.paramNames.contains(param) &&
!explicit.containsKey(param) &&
!boundArgs.containsValue(param)) {
boundArgs[i] = param;
binders.add(RouteParamBinder(param, find));
}
}
List<String> params = <String>[
for (String param in route.paramNames)
if (!boundArgs.containsValue(param)) param,
];
List<ControllerArg> args = <ControllerArg>[];
int paths = 0;
bool hasDto = false;
for (int i = 0; i < types.length; i++) {
String type = types[i];
bool nullable = type.endsWith('?');
String name = _withoutNull(type);
String? boundParam = boundArgs[i];
if (boundParam != null) {
args.add(ControllerArg(ArgKind.bound, param: boundParam));
continue;
}
if (name == 'DoxRequest' || name == 'IDoxRequest') {
args.add(const ControllerArg(ArgKind.request));
continue;
}
// an untyped first parameter `(req)` is the request
if (name == 'dynamic' && i == 0 && !types.contains('DoxRequest')) {
args.add(const ControllerArg(ArgKind.request));
continue;
}
PathType? pathType = name == 'dynamic' || name == 'Object'
? PathType.string
: _pathTypes[name];
RouteBinder<Object>? model = models[i];
if (pathType != null || model != null) {
if (paths >= params.length) {
throw StateError(
'Route $where: the controller has more path params than the '
'route has {...} params (${params.length}).',
);
}
String param = params[paths++];
if (model != null) {
if (!explicit.containsKey(param)) {
binders.add(RouteParamBinder(param, model));
}
args.add(ControllerArg(ArgKind.bound, param: param));
continue;
}
args.add(
ControllerArg(
ArgKind.path,
pathType: pathType!,
nullable: nullable || name == 'dynamic',
param: param,
),
);
continue;
}
DtoSchema<Object>? dto = Dox().dtos.ofName(name);
if (dto != null) {
if (hasDto) {
throw StateError(
'Route $where: a controller can have one @Dto only.',
);
}
hasDto = true;
args.add(ControllerArg(ArgKind.dto, dto: dto));
continue;
}
Type? injected = ioc.typeNamed(name);
if (injected != null) {
args.add(
ControllerArg(
ArgKind.inject,
type: injected,
value: ioc.isShared(injected) ? ioc.getByType(injected) : null,
),
);
continue;
}
throw StateError(
'Route $where: the controller parameter type $type is not known. '
'Use DoxRequest, a @Dto class, an @Inject class, a @DoxModel, a '
'type from Route.bind, or String, int, double, num, bool for path '
'params. Did you run `dart run build_runner build` and set '
'`dtos: dtos` and `container: registerContainer` in AppConfig?',
);
}
return ControllerPlan(fn, args, binders);
}