express-openapi-validator icon indicating copy to clipboard operation
express-openapi-validator copied to clipboard

Error in Custom Operation Resolver

Open linqFR opened this issue 3 years ago • 1 comments

Describe the bug In the example given for custom operation resolver, we can read:

new OpenApiValidator({
  apiSpec,
  operationHandlers: {
    basePath: path.join(__dirname, 'routes'),
    resolver: (basePath, route) => {
      // Pluck controller and function names from operationId
      const [controllerName, functionName] = route.schema['operationId'].split('.')
      // Get path to module and attempt to require it
      const modulePath = path.join(basePath, controllerName);
      const handler = require(modulePath)
      // Simplistic error checking to make sure the function actually exists
      // on the handler module
      if (handler[functionName] === undefined) {
        throw new Error(
          `Could not find a [${functionName}] function in ${modulePath} when trying to route [${route.method} ${route.expressRoute}].`
        )
      }
      // Finally return our function
      return handler[functionName]
    }
});

So route is supposed to have a schema property. We can only access:

{
  basePath: "..."
  expressRoute: "..."
  method: "GET"
  openApiRoute: "..."
  pathParams: []
}

Instead resolver has 3 arguments : basePath (which is the baseDirectory, not route.basePath), route AND apiDoc !

The example should mention apiDoc instead of route.schema.

linqFR avatar Apr 12 '21 17:04 linqFR

Yeah, I came across this issue as well and it should be noted that apiDoc is the full schema, not just the schema for this route. It wasn't exactly trivial to get the schema for the current route but I was able to get it. Below is the resolver I put together for async ESM modules. Hopefully this can help someone encountering the same issue!

import path from "path"

const esmresolver = basePath => {
    return {
        basePath,
        resolver: (basePath, route, apiDoc) => {
            const pathKey = route.openApiRoute.substring(route.basePath.length)
            const schema = apiDoc.paths[pathKey][route.method.toLowerCase()]

            // x-eov-operation-id takes priority over operationId
            const fn = schema["x-eov-operation-id"] || schema["operationId"]

            // x-eov-operation-handler with fallback to routes.js
            const handler = schema["x-eov-operation-handler"] || "routes"

            const handlerFile = `${handler}.js`
            const modP = import(path.join(basePath, handlerFile))

            return async (req, res, next) => {
                try {
                    const mod = await modP
                    mod[fn](req, res)
                } catch (err) {
                    console.error(err)
                    next(new Error(`Routing error ${handlerFile}:${fn}`))
                }
            }
        }
    }
}

export default esmresolver

This can be used like this:

app.use(
    OpenApiValidator.middleware({
        apiSpec,
        validateResponses: true,
        operationHandlers: esmresolver(modulePath)
    })
)

andyvanee avatar Jun 09 '21 18:06 andyvanee