Designing a back-end framework : callbacks types

Hi,
I am currently working on a back-end framework implemented with Opium.
The main point of this framework is to declare the routes in tree-like structure and then be able to generate a describeApi endpoint that describe all the routes of the framework.

A main point of the framework is that routes are defined with two callback. the hookCallback is intended to retrieve various attributes from the request (as taskId header, etc…). the callback is user-defined and it is here that the actual process of the request happens.

The two callbacks are designed to be chained together. As this piece of code shows
microService.ml

class microService = object(s)

  method routerDriver (app:Opium.App.t) =
    ...

  method routeDriver (route:RouteManager.routeEndpoint) (app:Opium.App.t)=
    App.action route.method_ route.path (fun r -> route.endpoint.callbacks.hookCallback r >>= (fun hookResponse -> route.endpoint.callbacks.callback (r, hookResponse)))

  method listen () = 
...
end

but what is a route endpoint ? it comes from the routeManager, which is responsible to register all the route and supply them when needed
routeManager.ml

type 'a routeEndpoint = {
  path:string;
  method_:Opium.Method.t;
  endpoint: 'a Route.exposedRoute
}

class ['a] routeManager = object(s)
 ...

  method add (routeObj:'a Route.exposedRoute) = ...
  method getRouteList () : 'a routeEndpoint list = ...
...
end

let routeManager = new routeManager;;

the type exposedRoute comes from the file Route.ml where the structure is like

type 'a callbacks = {
  callback: Request.t * 'a -> Response.t Lwt.t;
  hookCallback: Request.t ->  'a Lwt.t;
}

type 'a exposedRoute = {
  method_: Method.t;
  route:string;
  description: string;
  parameters: Parameters.exposedParameters;
  returns: Returns.exposedReturns;
  callbacks : 'a callbacks;
  componentId: string option;
  componentInputId: string option;
  componentOutputId: string option;
  purpose: string;
  nature: string option;
}

class ['a] route method_ route = object(s)
   method setDescription d = ...
   method setHookCallBack hc =...
   method setCallback c =...
   method expose () = ...
...
end

The route object is used to defined the route attributes and then build the exposed route type with the expose method.

  1. my issue is that I have to chain the hookCallback and user callback. Then the hookCallback produce data consumed by the userCallback. the hookCallback is defined at a higher level in the framework, so I do not know in advance the data it produces.
  2. My first solution was to used parametric polymorphism. So the types are defined with a type parameter 'a that matches with the data produced by the hookCallback.
  3. But this solution has two drawbacks. First, all the routes are linked with the same data produced. All the hookCallbacks for every routes have to produce the same data type 'a, which seems restrictive even if it could work.
  4. Second, it triggers a error in the routeManager :
99 | let routeManager = new routeManager;;
         ^^^^^^^^^^^^
Error: The type of this expression, '_weak1 routeManager,
       contains type variables that cannot be generalized

So I cannot declare the routeManager without the implied 'a type. I would declare the routeManager here so it is too restrictive.
5. I have made an other attempt with the 'a. quantifier
route.ml

type 'a callbacks = {
  callback: Request.t * 'a -> Response.t Lwt.t;
  hookCallback: Request.t ->  'a Lwt.t;
}

type exposedRoute = {
  method_: Method.t;
  route:string;
  description: string;
  parameters: Parameters.exposedParameters;
  returns: Returns.exposedReturns;
  callbacks : 'a.'a callbacks;
  componentId: string option;
  componentInputId: string option;
  componentOutputId: string option;
  purpose: string;
  nature: string option;
}

but I get an error in my expose method, which I do not get :

68 |         callbacks = {callback; hookCallback};
                         ^^^^^^^^^^^^^^^^^^^^^^^^
Error: This field value has type 'a callbacks which is less general than
         'a0. 'a0 callbacks

How can I declare a type 'a that is specific to every route ?

The error with weak type variables only occurs because the let routeManager defines a top level module component, and there is no type to infer for this component. The same piece of code will work as a REPL statement, or if a .mli file is added in which val routeManager does not occur (or has a concrete instantiation for the type parameter).

You probably want the 'a to be existentially quantified within the callback type, so using GADT syntax:

    type callback = Callback : { callback : Request.t * 'a -> Response.t Lwt.t; hookCallback: Request.t -> 'a Lwt.t} -> callback;;

Are you trying for some kind of builder pattern? If the callback and hookCallback need to have matching types, you cannot provide them one at a time, as setHookCallback and setCallback would imply.