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