@360mediadirect/express-openapi-validator 中文文档教程

发布于 4年前 浏览 36 项目主页 更新于 3年前

express-openapi-validator

 所有贡献者覆盖状态Codacy BadgeGitpod准备代码

ExpressJS 的 OpenApi 验证器,自动验证 API 请求响应 使用 OpenAPI 3 规范。

????express-openapi-validator 是一个独立的库,可与新的和现有的 API 应用程序集成。 express-openapi-validator 让您可以按照自己的方式编写代码; 它不强加任何编码约定或项目布局。 只需将验证器安装到您的 Express 应用程序上,将其指向您的 OpenAPI 3 规范,然后按照您喜欢的方式定义和实施路由。 查看示例

特性:

  • ✔️ request validation
  • ✔️ response validation (json only)
  • security validation / custom security functions
  • 3rd party / custom formats
  • optionally auto-map OpenAPI endpoints to Express handler functions
  • ✂️ \$ref support; split specs over multiple files
  • file upload

GitHub starsTwitter URL

Install

npm install express-openapi-validator

Usage

  1. Require/import the openapi validator
const OpenApiValidator = require('express-openapi-validator');
  1. Install the middleware
app.use(
  OpenApiValidator.middleware({
    apiSpec: './openapi.yaml',
    validateRequests: true, // (default)
    validateResponses: true, // false by default
  }),
);
  1. Register an error handler
app.use((err, req, res, next) => {
  // format error
  res.status(err.status || 500).json({
    message: err.message,
    errors: err.errors,
  });
});

重要:确保 express 配置了所有相关的正文解析器。 主体解析器中间件函数必须在任何经过​​验证的路由之前指定。 查看示例

Upgrading from 3.x

在 v4.xx 中,验证器作为标准连接中间件使用 app.use(...) 和/或 router.use(...) 安装(示例)。 这与 v3.xx 不同,后者需要 install 方法进行安装。 install 方法在 v4 中不再存在。

Usage (options)

请参阅高级用法选项:

  • inline api specs as JSON.
  • configure request/response validation options
  • customize authentication with security validation handlers.
  • use OpenAPI 3.0.x 3rd party and custom formats.
  • tweak the file upload configuration.
  • ignore routes
  • and more…

Example Express API Server

下面演示了如何使用 express-openapi-validator 自动验证请求和响应。 它还包括文件上传!

查看完整的源代码OpenAPI spec 以下示例:

const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const http = require('http');
const app = express();

// 1. Import the express-openapi-validator library
const OpenApiValidator = require('express-openapi-validator');

// 2. Set up body parsers for the request body types you expect
//    Must be specified prior to endpoints in 5.
app.use(bodyParser.json());
app.use(bodyParser.text());
app.use(bodyParser.urlencoded({ extended: false }));

// 3. (optionally) Serve the OpenAPI spec
const spec = path.join(__dirname, 'api.yaml');
app.use('/spec', express.static(spec));

// 4. Install the OpenApiValidator onto your express app
app.use(
  OpenApiValidator.middleware({
    apiSpec: './api.yaml',
    validateResponses: true, // <-- to validate responses
    // unknownFormats: ['my-format'] // <-- to provide custom formats
  }),
);

// 5. Define routes using Express
app.get('/v1/pets', function (req, res, next) {
  res.json([
    { id: 1, type: 'cat', name: 'max' },
    { id: 2, type: 'cat', name: 'mini' },
  ]);
});

app.post('/v1/pets', function (req, res, next) {
  res.json({ name: 'sparky', type: 'dog' });
});

app.get('/v1/pets/:id', function (req, res, next) {
  res.json({ id: req.params.id, type: 'dog', name: 'sparky' });
});

// 5a. Define route(s) to upload file(s)
app.post('/v1/pets/:id/photos', function (req, res, next) {
  // files are found in req.files
  // non-file multipart params can be found as such: req.body['my-param']
  res.json({
    files_metadata: req.files.map((f) => ({
      originalname: f.originalname,
      encoding: f.encoding,
      mimetype: f.mimetype,
      // Buffer of file conents
      buffer: f.buffer,
    })),
  });
});

// 6. Create an Express error handler
app.use((err, req, res, next) => {
  // 7. Customize errors
  console.error(err); // dump error to console for debug
  res.status(err.status || 500).json({
    message: err.message,
    errors: err.errors,
  });
});

http.createServer(app).listen(3000);

Example Express API Server: with operationHandlers

不想手动将您的 OpenAPI 端点映射到快递处理函数? express-openapi-validator 可以自动为您完成!

使用 express-openapi-validator 的 OpenAPI x-eov-operation-* 供应商扩展。 查看带有源代码OpenAPI 规范

这是要点< /strong>

  • First, specifiy the operationHandlers option to set the base directory that contains your operation handler files.
app.use(
  OpenApiValidator.middleware({
    apiSpec,
    operationHandlers: path.join(__dirname),
  }),
);
  • Next, use the x-eov-operation-id OpenAPI vendor extension or operationId to specify the id of operation handler to invoke.
/ping:
  get:
    # operationId: ping
    x-eov-operation-id: ping
  • Next, use the x-eov-operation-handler OpenAPI vendor extension to specify a path (relative to operationHandlers) to the module that contains the handler for this operation.
/ping:
  get:
    x-eov-operation-id: ping
    x-eov-operation-handler: routes/ping # no .js or .ts extension
  • Finally, create the express handler module e.g. routes/ping.js
module.exports = {
  // the express handler implementaiton for ping
  ping: (req, res) => res.status(200).send('pong'),
};

注意:一个文件可能包含一个多个 处理程序。

以下是一些代码片段:

app.js

const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const logger = require('morgan');
const http = require('http');
const OpenApiValidator = require('express-openapi-validator');

const port = 3000;
const app = express();
const apiSpec = path.join(__dirname, 'api.yaml');

// 1. Install bodyParsers for the request types your API will support
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.text());
app.use(bodyParser.json());

app.use(logger('dev'));

app.use('/spec', express.static(apiSpec));

//  2. Install the OpenApiValidator on your express app
app.use(
  OpenApiValidator.middleware({
    apiSpec,
    validateResponses: true, // default false
    // 3. Provide the base path to the operation handlers directory
    operationHandlers: path.join(__dirname), // default false
  }),
);

// 4. Woah sweet! With auto-wired operation handlers, I don't have to declare my routes!
//    See api.yaml for x-eov-* vendor extensions

// 5. Create a custom error handler
app.use((err, req, res, next) => {
  // format errors
  res.status(err.status || 500).json({
    message: err.message,
    errors: err.errors,
  });
});

http.createServer(app).listen(port);
console.log(`Listening on port ${port}`);

module.exports = app;

api.yaml

/ping:
  get:
    description: |
      ping then pong!
    # OpenAPI's operationId may be used to to specify the operation id
    operationId: ping
    # x-eov-operation-id may be used to specify the operation id
    # Used when operationId is omiited. Overrides operationId when both are specified
    x-eov-operation-id: ping
    # specifies the path to the operation handler.
    # the path is relative to the operationHandlers option
    # e.g. operations/base/path/routes/ping.js
    x-eov-operation-handler: routes/ping
    responses:
      '200':
        description: OK
        # ...

ping.js

module.exports = {
  // ping must match operationId or x-eov-operation-id above
  // note that x-eov-operation-id overrides operationId
  ping: (req, res) => res.status(200).send('pong'),
};

API Validation Response Examples

Validates a query parameter with a value constraint

curl -s http://localhost:3000/v1/pets/as |jq
{
  "message": "request.params.id should be integer",
  "errors": [
    {
      "path": ".params.id",
      "message": "should be integer",
      "errorCode": "type.openapi.validation"
    }
  ]
}

Validates a query parameter with a range constraint

 curl -s 'http://localhost:3000/v1/pets?limit=25' |jq
{
  "message": "request.query should have required property 'type', request.query.limit should be <= 20",
  "errors": [
    {
      "path": ".query.type",
      "message": "should have required property 'type'",
      "errorCode": "required.openapi.validation"
    },
    {
      "path": ".query.limit",
      "message": "should be <= 20",
      "errorCode": "maximum.openapi.validation"
    }
  ]
}

Validates securities e.g. API Key

 curl -s --request POST \
  --url http://localhost:3000/v1/pets \
  --data '{}' |jq
{
  "message": "'X-API-Key' header required",
  "errors": [
    {
      "path": "/v1/pets",
      "message": "'X-API-Key' header required"
    }
  ]
}

提供标头通过 OpenAPI 验证。

注意:您的 Express 中间件或端点逻辑随后可以提供额外的检查。

curl -XPOST http://localhost:3000/v1/pets \
  --header 'X-Api-Key: XXXXX' \
  --header 'content-type: application/json' \
  -d '{"name": "spot"}' | jq

{
  "id": 4,
  "name": "spot"
}

Validates content-type

curl -s --request POST \
  --url http://localhost:3000/v1/pets \
  --header 'content-type: application/xml' \
  --header 'x-api-key: XXXX' \
  --data '{
        "name": "test"
}' |jq
  "message": "unsupported media type application/xml",
  "errors": [
    {
      "path": "/v1/pets",
      "message": "unsupported media type application/xml"
    }
  ]
}

Validates a POST request body

curl -s --request POST \
  --url http://localhost:3000/v1/pets \
  --header 'content-type: application/json' \
  --header 'x-api-key: XXXX' \
  --data '{}'|jq
{
  "message": "request.body should have required property 'name'",
  "errors": [
    {
      "path": ".body.name",
      "message": "should have required property 'name'",
      "errorCode": "required.openapi.validation"
    }
  ]
}

File Upload (out of the box)

curl -XPOST http://localhost:3000/v1/pets/10/photos -F file=@app.js|jq
{
  "files_metadata": [
    {
      "originalname": "app.js",
      "encoding": "7bit",
      "mimetype": "application/octet-stream"
    }
  ]
}

Validates responses (optional)

响应验证错误返回 500,而不是 400

不匹配的响应

 curl -s 'http://localhost:3000/v1/pets/99' |jq
{
  "message": ".response should have required property 'name', .response should have required property 'id'",
  "errors": [
    {
      "path": ".response.name",
      "message": "should have required property 'name'",
      "errorCode": "required.openapi.validation"
    },
    {
      "path": ".response.id",
      "message": "should have required property 'id'",
      "errorCode": "required.openapi.validation"
    }
  ]
}

…and much more. Try it out!

Response status codes

/v1/pets/99 将返回与规范express-openapi -validator 根据情况返回以下错误代码。

Request validation (validateRequests=true)

statuswhen
400 (bad request)a validation error is encountered
401 (unauthorized)a security / authentication errors is encountered e.g. missing api-key, Authorization header, etc
404 (not found)a path is not found i.e. not declared in the API spec
405 (method not allowed)a path is declared in the API spec, but a no schema is provided for the method

Response validation (validateResponses=true)

statuswhen
500 (internal server error)any error is encountered by the validator

Advanced Usage

OpenApiValidator Middleware Options

express-openapi 验证器通过其选项提供了很大的灵活性。

选项通过选项对象提供。 选项采用以下形式:

OpenApiValidator.middleware({
  apiSpec: './openapi.yaml',
  validateRequests: true,
  validateResponses: true,
  validateSecurity: {
    handlers: {
      ApiKeyAuth: (req, scopes, schema) => {
        throw { status: 401, message: 'sorry' }
      }
    }
  },
  validateFormats: 'fast',
  formats: [{
    name: 'my-custom-format',
    type: 'string' | 'number',
    validate: (value: any) => boolean,
  }],
  unknownFormats: ['phone-number', 'uuid'],
  operationHandlers: false | 'operations/base/path' | { ... },
  ignorePaths: /.*\/pets$/,
  fileUploader: { ... } | true | false,
  $refParser: {
    mode: 'bundle'
  }
});

▪️ apiSpec (required)

指定 OpenAPI 3 规范的路径或表示 OpenAPI 3 规范的 JSON 对象

apiSpec: './path/to/my-openapi-spec.yaml';

  apiSpec: {
  openapi: '3.0.1',
  info: {...},
  servers: [...],
  paths: {...},
  components: {
    responses: {...},
    schemas: {...}
  }
}

▪️ validateRequests (optional)

确定验证器是否应验证请求。

  • true默认)- 验证请求。

  • false - 不验证请求。

  • { ... } - 使用选项验证请求

    allowUnknownQueryParameters:

  • true - 允许未知/未声明的查询参数通过验证

  • false - (default) 如果存在未知查询参数则验证失败

    例如:

  validateRequests: {
    allowUnknownQueryParameters: true;
  }

allowUnknownQueryParameters 是为整个验证器设置的。 可以使用每次操作覆盖它 自定义属性 x-allow-unknown-query-parameters

例如,仅在单个端点上允许未知查询参数:

  paths:
    /allow_unknown:
      get:
        x-allow-unknown-query-parameters: true
        parameters:
          - name: value
            in: query
            schema:
              type: string
        responses:
          200:
            description: success

coerceTypes:

确定验证器是否将强制请求主体。 默认情况下,请求查询和路径参数、标头、cookie 是强制的,此设置不会影响它。

选项:

  • true - 强制标量数据类型。

  • false -(默认)不强制类型。 (更严格、更安全)

  • "array" - 除了标量类型之间的强制转换之外,将标量数据强制转换为具有一个元素的数组,反之亦然(根据架构的要求)。

    例如:

  validateRequests: {
    coerceTypes: true;
  }

▪️ validateResponses (optional)

确定验证器是否应验证响应。 还接受响应验证选项。

  • true - 在“严格”模式下验证响应,即响应必须与模式匹配。

  • false默认)- 不验证响应

  • { ... } - 使用选项验证响应

    removeAdditional:

  • < code>"failing" - 架构验证失败的其他属性会自动从响应中删除。

    coerceTypes:

  • true - 强制标量数据类型。

  • false -(默认)不强制类型。 (几乎总是期望的行为)

  • "array" - 除了标量类型之间的强制转换,将标量数据强制转换为具有一个元素的数组,反之亦然(根据模式的要求)。

    例如:

  validateResponses: {
    removeAdditional: 'failing';
  }

onError:

将在响应验证错误时调用的函数,而不是默认处理。 如果您想记录错误或发出指标,但又不想实际使请求失败,则很有用。 接收验证错误和有问题的响应正文。

例如:

  validateResponses: {
    onError: (error, body) => {
      console.log(`Response body fails validation: `, error);
      console.debug(body);
    }
  }

▪️ validateSecurity (optional)

确定验证器是否应验证安全性,例如 apikey、basic、oauth2、openid 等

  • truedefault)- 验证安全性

  • false -不验证安全性

  • { ... } - 使用 handlers 验证安全性。 请参阅 安全处理程序 文档。

    处理程序:

    例如:

  validateSecurity: {
    handlers: {
      ApiKeyAuth: function(req, scopes, schema) {
        console.log('apikey handler throws custom error', scopes, schema);
        throw Error('my message');
      },
    }
  }

▪️ formats (optional)

定义客户格式列表。

  • [{ ... }] - array of custom format objects. Each object must have the following properties:
  • name: string (required) - the format name
  • validate: (v: any) => boolean (required) - the validation function
  • type: 'string' | 'number' (optional) - the format's type

eg

formats: [
  {
    name: 'my-three-digit-format',
    type: 'number',
    // validate returns true the number has 3 digits, false otherwise
    validate: (v) => /^\d{3}$/.test(v.toString()),
  },
  {
    name: 'my-three-letter-format',
    type: 'string',
    // validate returns true the string has 3 letters, false otherwise
    validate: (v) => /^[A-Za-z]{3}$/.test(v),
  },
];

然后在规范中使用它 eg

my_property:
  type: string
  format: my-three-letter-format'

▪️ validateFormats (optional)

指定字符串格式验证的严格性。

  • "fast" (default) - only validate syntax, but not semantics. E.g. 2010-13-30T23:12:35Z will pass validation eventhough it contains month 13.
  • "full" - validate both syntax and semantics. Illegal dates will not pass.
  • false - do not validate formats at all.

▪️ unknownFormats (optional)

定义验证器在遇到未知或自定义格式时的行为方式。

  • true (default) - When an unknown format is encountered, the validator will report a 400 error.
  • [string] (recommended for unknown formats) - An array of unknown format names that will be ignored by the validator. This option can be used to allow usage of third party schemas with format(s), but still fail if another unknown format is used. e.g.
  unknownFormats: ['phone-number', 'uuid'];
  • "ignore" - to log warning during schema compilation and always pass validation. This option is not recommended, as it allows to mistype format name and it won't be validated without any error message.

▪️ operationHandlers (optional)

定义操作处理程序的基本目录。 这与 express-openapi-validator 的 OpenAPI 供应商扩展、x-eov-operation-idx-eov-operation-handler 和 OpenAPI 的 operationId 结合使用。 请参阅示例

此外,如果您想更改模块的解析方式,例如使用点分隔的操作 ID,例如 path.to.module.myFunction,您可以选择添加自定义 resolver。 请参阅文档和示例

  • string - 包含操作处理程序的基本目录

  • false -(默认)禁用自动连接操作处理程序

  • { ... } - 指定基本目录和可选的自定义解析器

    handlers:

    例如:

  operationHandlers: {
    basePath: __dirname,
    resolver: function (modulePath, route): express.RequestHandler {
      ///...
    }
  }
operationHandlers: 'operations/base/path'

注意x-eov-operation-handler OpenAPI 供应商扩展指定了一个相对于operationHandlers 的路径. 因此,如果 operationHandlers/handlers 并且 x-eov-operation-handler 具有路径 routes/ping,那么使用处理程序文件 /handlers/routes/ping.js(或 ts)。

完整示例此处

api.yaml

/ping:
  get:
    description: |
      ping then pong!
    # OpenAPI's operationId may be used to to specify the operation id
    operationId: ping
    # x-eov-operation-id may be used to specify the operation id
    # Used when operationId is omiited. Overrides operationId when both are specified
    x-eov-operation-id: ping
    # specifies the path to the operation handler.
    # the path is relative to the operationHandlers option
    # e.g. operations/base/path/routes/ping.js
    x-eov-operation-handler: routes/ping
    responses:
      '200':
        description: OK
        # ...

routes/ping.js

x-eov-operation-handler 指定此处理程序文件的路径,ping.js

x- eov-operation-id(或operationId)指定操作处理程序的键,例如ping

module.exports = {
  ping: (req, res) => res.status(200).send('pong'),
};

▪️ ignorePaths (optional)

定义一个正则表达式或函数来确定是否应忽略路径. 如果它是一个正则表达式,任何匹配正则表达式的路径都会被验证器忽略。 如果它是一个函数,它将忽略任何返回真值的路径。

以下忽略任何以 /pets 结尾的路径,例如 /v1/pets。 作为正则表达式:

ignorePaths: /.*\/pets$/

或作为函数:

ignorePaths: (path) => path.endsWith('/pets')

▪️ fileUploader (optional)

指定传递给 multer 的选项。 express-openapi-validator 使用 multer 来处理文件上传。 请参阅 multer opts

  • true默认)- 启用 multer 并提供简单的文件(s) 上传功能

  • false - 禁用文件上传功能。 用户可以提供上传功能

  • {...} - 要传递给 multer 的 multer 选项。 查看 multer opts 了解可能的选项,

    例如

  fileUploader: {
    dest: 'uploads/';
  }

▪️ \$refParser.mode (optional)

确定内部 json-schema-ref-parser 如何解析 JSON 模式引用。 通常,默认模式 bundle 就足够了,但是如果您在 \$refs 中使用 转义字符取消引用 是必要的。

  • bundle (default) - Bundles all referenced files/URLs into a single schema that only has internal $ref pointers. This eliminates the risk of circular references, but does not handle escaped characters in $refs.
  • dereference - Dereferences all $ref pointers in the JSON Schema, replacing each reference with its resolved value. Introduces risk of circular $refs. Handles escape characters in \$refs)

有关详细信息,请参阅此问题

例如

$refParser: {
  mode: 'bundle';
}

▪️ coerceTypes (optional) - deprecated

,确定验证器是否应强制值类型与 OpenAPI 规范中定义的值类型相匹配。 此选项应用于路径参数、查询字符串、标头和 cookie。 想要禁用此功能极不可能。 因此,此选项已被弃用,并将在下一个主要版本中删除

  • true (default) - coerce scalar data types.
  • "array" - in addition to coercions between scalar types, coerce scalar data to an array with one element and vice versa (as required by the schema).

The Base URL

验证器将仅验证请求、证券和响应 服务器的基本 URL

当 API 和前端由同一个服务时,这很有用 应用。 (有关基本 URL 的更多详细信息。)

servers:
  - url: https://api.example.com/v1

验证适用于在下面定义的所有路径这个基本网址。 您应用中的路线 _not_se URL(例如页面)将不会被验证。

URLValidated?
https://api.example.com/v1/users:whitecheckmark:
https://api.example.com/index.htmlno; not under the base URL

在某些情况下,可能需要跳过验证 在基本 url 下 的路径。 为此,请使用 ignorePaths 选项。

Security handlers

注意:安全处理程序是一个可选组件。 security handlers 提供了一种便利,请求、声明的范围和安全模式本身作为参数提供给您定义的每个安全 handlers 回调。 您在每个回调中编写的代码随后可以执行身份验证和授权检查。 请注意,同样可以使用标准 Express 中间件实现。 区别 是安全处理程序 为您提供规范中描述的 OpenAPI 架构数据。 最终,这意味着您不必在代码中复制该信息。

总而言之,安全 handlers 完全是可选的,并且是为了方便而提供的。

安全处理程序指定一组自定义安全处理程序,用于验证安全性,即身份验证和授权。 如果指定了证券 handlers 对象,则必须为所有 证券定义一个处理程序。 如果指定安全`处理程序,则始终使用默认处理程序。 默认处理程序将根据 OpenAPI 规范进行验证,然后调用下一个中间件。

如果指定了安全 handlers,验证器将根据 OpenAPI 规范进行验证,然后调用安全处理程序,向其提供 Express 请求、安全范围和安全模式对象。

  • security handlers is an object that maps security keys to security handler functions. Each security key must correspond to securityScheme name. The validateSecurity.handlers object signature is as follows:
  {
    validateSecurity: {
      handlers: {
        [securityKey]: function(
          req: Express.Request,
          scopes: string[],
          schema: SecuritySchemeObject
        ): void,
      }
    }
  }

SecuritySchemeObject

例如:

  validateSecurity: {
    handlers: {
      ApiKeyAuth: function(req, scopes, schema) {
        console.log('apikey handler throws custom error', scopes, schema);
        throw Error('my message');
      },
    }
  }

express-openapi-validator 在委托给安全处理程序之前执行基本的验证传递。 如果基本验证通过,则调用安全处理函数。

为了发出身份验证失败的信号,安全处理程序函数必须

  1. throw { status: 403, message: 'forbidden' }
  2. throw Error('optional message')
  3. return false
  4. return a promise which resolves to false e.g Promise.resolve(false)
  5. return a promise rejection e.g.
  • Promise.reject({ status: 401, message: 'yikes' });
  • Promise.reject(Error('optional 'message')
  • Promise.reject(false)

注意:返回错误状态401,除非上面的选项i.是使用

一些示例:

validateSecurity: {
  handlers: {
    ApiKeyAuth: (req, scopes, schema) => {
      throw Error('my message');
    },
    OpenID: async (req, scopes, schema) => {
      throw { status: 403, message: 'forbidden' }
    },
    BasicAuth: (req, scopes, schema) => {
      return Promise.resolve(false);
    },
    ...
  }
}

为了授予authz,处理函数必须

  • return true
  • return a promise which resolves to true

一些示例

validateSecurity: {
  handlers: {
    ApiKeyAuth: (req, scopes, schema) => {
      return true;
    },
    BearerAuth: async (req, scopes, schema) => {
      return true;
    },
    ...
  }
}

每个安全handlers' securityKey 必须匹配 components/securitySchemes 属性

components:
  securitySchemes:
    ApiKeyAuth: # <-- Note this name must be used as the name handler function property
      type: apiKey
      in: header
      name: X-API-Key

请参阅 OpenAPI 3 securitySchemesecurity 文档的身份验证 请参阅单元测试中的示例

Example: Multiple Validators and API specs

它可能对通过单个服务为具有不同规范的多个 API 提供服务。 一个示例可能是一个 API,它从同一服务为 v1v2 提供服务。 下面的示例代码显示了如何实现这一点。

查看完整的示例

const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const http = require('http');
const OpenApiValidator = require('express-openapi-validator');

app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.text());
app.use(bodyParser.json());

const versions = [1, 2];

for (const v of versions) {
  const apiSpec = path.join(__dirname, `api.v${v}.yaml`);
  app.use(
    OpenApiValidator.middleware({
      apiSpec,
    }),
  );

  routes(app, v);
}

http.createServer(app).listen(3000);
console.log('Listening on port 3000');

function routes(app, v) {
  if (v === 1) routesV1(app);
  if (v === 2) routesV2(app);
}

function routesV1(app) {
  const v = '/v1';
  app.post(`${v}/pets`, (req, res, next) => {
    res.json({ ...req.body });
  });
  app.get(`${v}/pets`, (req, res, next) => {
    res.json([
      {
        id: 1,
        name: 'happy',
        type: 'cat',
      },
    ]);
  });

  app.use((err, req, res, next) => {
    // format error
    res.status(err.status || 500).json({
      message: err.message,
      errors: err.errors,
    });
  });
}

function routesV2(app) {
  const v = '/v2';
  app.get(`${v}/pets`, (req, res, next) => {
    res.json([
      {
        pet_id: 1,
        pet_name: 'happy',
        pet_type: 'kitty',
      },
    ]);
  });
  app.post(`${v}/pets`, (req, res, next) => {
    res.json({ ...req.body });
  });

  app.use((err, req, res, next) => {
    // format error
    res.status(err.status || 500).json({
      message: err.message,
      errors: err.errors,
    });
  });
}

module.exports = app;

FAQ

问: 如何匹配路径,如 RFC-6570 中所述?

答:OpenAPI 3.0 不支持 RFC-6570。 也就是说,我们提供了一种在语法上符合 OpenAPI 3 并完成常见用例的简约机制。 例如,匹配文件路径并将匹配的路径存储在 req.params

使用以下 OpenAPI 3.x 定义

/files/{path}*:
  get:
    parameters:
      - name: path
        in: path
        required: true
        schema:
          type: string

使用以下 Express 路由定义

  app.get(`/files/:path(*)`, (req, res) => { /* do stuff */ }`

A path like /files/some/long/path 将通过验证。 Express req.params.path 属性将保存值 some/long/path

问: 我可以将鉴别器与 oneOfanyOf 一起使用吗?

答: 目前,支持顶级鉴别器。 请参阅顶级鉴别器示例

Q : securityHandlers 属性发生了什么变化?

答:在 v3 中,securityHandlers 已被 validateSecurity.handlers 取代。 要使用 v3 安全处理程序,请将现有的安全处理程序移至新属性。 不需要进行其他更改。 请注意 v2 securityHandlers 属性在 v3 中受支持,但已弃用

multerOpts 属性发生了什么变化?

A:在 v3 中,multerOpts 已被 fileUploader 取代。 为了使用 v3 fileUploader,将您的 multer 选项移动到 fileUploader 不需要其他更改。 请注意 v2 multerOpts 属性在 v3 中受支持,但已弃用

Q: 我可以使用 allowUnknownQueryParameters: false 禁止未知查询参数。 如何禁止未知的身体参数?

A:描述additionalProperties: false > 例如 requestBody 以确保不允许使用其他属性。 例如:

Pet:
additionalProperties: false
required:
  - name
properties:
  name:
    type: string
  type:
    type: string

问:我从 v2 升级到 v3,验证不再有效。 我如何解决它?

A:在2.xx版本中,install方法是同步执行的,在3.x版本中是异步执行的。 要在 v3 中获得 v2 行为,请使用 installSync 方法。 有关详细信息,请参阅同步部分。

问: 我可以将 express-openapi-validatorswagger-ui-express 一起使用吗?

答:是的。 在安装 OpenApiValidator 之前,请务必使用 swagger-ui-express 服务中间件。 这将确保 swagger-ui-express 能够在 OpenApiValidator 尝试使用它之前充分准备规范。 例如:

const swaggerUi = require('swagger-ui-express')
const OpenApiValidator = require('express-openapi-validator')

...

app.use('/', swaggerUi.serve, swaggerUi.setup(documentation))

app.use(OpenApiValidator.middleware({
  apiSpec, // api spec JSON object
  //... other options
  }
}))

问: 我在 express.Router 上定义了一个处理函数。 如果我调用 req.params,每个参数值的类型都是 string。 如果我在 express.Application 上定义了相同的处理函数,则 req.params 中的每个值都已被强制转换为我的规范中声明的类型。 为什么不在 express.Router 上强制使用这些 F 值?

答:首先,请务必注意此行为不会影响验证。 验证器将根据您的规范中定义的类型进行验证。

为了修改 req.params,express 需要注册一个参数处理程序,例如 app.param(...)router.param(.. .)。 由于 app 可用于中间件函数,验证器注册一个 app.param 处理程序来强制和修改 req.params 的值到它们声明的类型。 不幸的是,express 不提供从中间件函数确定当前路由器的方法,因此验证器无法在 express 路由器上注册相同的参数处理程序。 最终,这意味着如果您的处理程序函数是在 app 上定义的,则 req.params 的值将被强制为其声明的类型。 如果您的处理程序函数是在 express.Router 上声明的,则 req.params 值的值将是 string 类型(您必须强制转换它们例如 parseInt(req.params.id))。

Contributors ✨

欢迎投稿! 以下是如何贡献

感谢这些优秀的人 (emoji key):


Carmine DiMascio

???? ⚠️ ????

Sheldhur Mornor

???? ⚠️

Andrey Trebler

???? ⚠️

richdouglasevans

????

Miran Setinc

????

Frank Calise

????

Gonen Dukas

???? ⚠️

Sven Eliasson

???? ⚠️

Spencer Brown

???? ⚠️

José Neves

????

mk811

???? ⚠️

HugoMario

???? ⚠️

Rowan Cockett

????

Jacques Yakoub

????

ckeboss

???? ⚠️

JacobLey

???? ⚠️

Dmitriy Voeykov

???? ⚠️

GARAMKIM

???? ????

Mathias Scherer

????

Mirek

????

Florian Beutel

????

jakubskopal

???? ⚠️ ????

Jordan Dobrev

⚠️ ????

Enrico Fabris

????

Dustin Wheeler

???? ???? ⚠️

Thomas Carmichael

????

Jakesterwars

????

xtrycatchx

????

Lee Dong Joo

????

Dmitry Chekanov

???? ⚠️

Redhart Azul

????

Joost Diepenmaat

???? ⚠️

Dom Parfitt

???? ⚠️

xg1990

????

ownagedj

???? ⚠️

David Garner

????

Balazs Soltesz

???? ⚠️

Christiaan Nieuwlaat

????

Ilya

???? ⚠️

Yuliya Bagriy

???? ⚠️

Kristjan Siimson

???? ⚠️

Guillaume

???? ⚠️

Volodymyr Kolesnykov

???? ⚠️

Pierre Le Roux

???? ⚠️ ????

Electro Type

????

这个项目遵循 all-contributors 规范。 欢迎任何形式的贡献!

Community articles, blogs, and tutorials

正在寻找内容创作者......

您是否撰写过使用 express-openapi-validator 的文章、博客或教程?

请将您的链接发布到此处

我们计划在这里公开各种链接。

License

麻省理工学院

给我买杯咖啡

???? express-openapi-validator

All ContributorsCoverage StatusCodacy BadgeGitpod Ready-to-Code

An OpenApi validator for ExpressJS that automatically validates API requests and responses using an OpenAPI 3 specification.

????express-openapi-validator is an unopinionated library that integrates with new and existing API applications. express-openapi-validator lets you write code the way you want; it does not impose any coding convention or project layout. Simply, install the validator onto your express app, point it to your OpenAPI 3 specification, then define and implement routes the way you prefer. See an example.

Features:

  • ✔️ request validation
  • ✔️ response validation (json only)
  • ???? security validation / custom security functions
  • ???? 3rd party / custom formats
  • ???? optionally auto-map OpenAPI endpoints to Express handler functions
  • ✂️ \$ref support; split specs over multiple files
  • ???? file upload

GitHub starsTwitter URL

Install

npm install express-openapi-validator

Usage

  1. Require/import the openapi validator
const OpenApiValidator = require('express-openapi-validator');
  1. Install the middleware
app.use(
  OpenApiValidator.middleware({
    apiSpec: './openapi.yaml',
    validateRequests: true, // (default)
    validateResponses: true, // false by default
  }),
);
  1. Register an error handler
app.use((err, req, res, next) => {
  // format error
  res.status(err.status || 500).json({
    message: err.message,
    errors: err.errors,
  });
});

Important: Ensure express is configured with all relevant body parsers. Body parser middleware functions must be specified prior to any validated routes. See an example.

Upgrading from 3.x

In v4.x.x, the validator is installed as standard connect middleware using app.use(...) and/or router.use(...) (example). This differs from the v3.x.x the installation which required the install method(s). The install methods no longer exist in v4.

Usage (options)

See Advanced Usage options to:

  • inline api specs as JSON.
  • configure request/response validation options
  • customize authentication with security validation handlers.
  • use OpenAPI 3.0.x 3rd party and custom formats.
  • tweak the file upload configuration.
  • ignore routes
  • and more…

Example Express API Server

The following demonstrates how to use express-openapi-validator to auto validate requests and responses. It also includes file upload!

See the complete source code and OpenAPI spec for the example below:

const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const http = require('http');
const app = express();

// 1. Import the express-openapi-validator library
const OpenApiValidator = require('express-openapi-validator');

// 2. Set up body parsers for the request body types you expect
//    Must be specified prior to endpoints in 5.
app.use(bodyParser.json());
app.use(bodyParser.text());
app.use(bodyParser.urlencoded({ extended: false }));

// 3. (optionally) Serve the OpenAPI spec
const spec = path.join(__dirname, 'api.yaml');
app.use('/spec', express.static(spec));

// 4. Install the OpenApiValidator onto your express app
app.use(
  OpenApiValidator.middleware({
    apiSpec: './api.yaml',
    validateResponses: true, // <-- to validate responses
    // unknownFormats: ['my-format'] // <-- to provide custom formats
  }),
);

// 5. Define routes using Express
app.get('/v1/pets', function (req, res, next) {
  res.json([
    { id: 1, type: 'cat', name: 'max' },
    { id: 2, type: 'cat', name: 'mini' },
  ]);
});

app.post('/v1/pets', function (req, res, next) {
  res.json({ name: 'sparky', type: 'dog' });
});

app.get('/v1/pets/:id', function (req, res, next) {
  res.json({ id: req.params.id, type: 'dog', name: 'sparky' });
});

// 5a. Define route(s) to upload file(s)
app.post('/v1/pets/:id/photos', function (req, res, next) {
  // files are found in req.files
  // non-file multipart params can be found as such: req.body['my-param']
  res.json({
    files_metadata: req.files.map((f) => ({
      originalname: f.originalname,
      encoding: f.encoding,
      mimetype: f.mimetype,
      // Buffer of file conents
      buffer: f.buffer,
    })),
  });
});

// 6. Create an Express error handler
app.use((err, req, res, next) => {
  // 7. Customize errors
  console.error(err); // dump error to console for debug
  res.status(err.status || 500).json({
    message: err.message,
    errors: err.errors,
  });
});

http.createServer(app).listen(3000);

Example Express API Server: with operationHandlers

Don't want to manually map your OpenAPI endpoints to Express handler functions? express-openapi-validator can do it for you, automatically!

Use express-openapi-validator's OpenAPI x-eov-operation-* vendor extensions. See a full example with source code and an OpenAPI spec

Here's the gist

  • First, specifiy the operationHandlers option to set the base directory that contains your operation handler files.
app.use(
  OpenApiValidator.middleware({
    apiSpec,
    operationHandlers: path.join(__dirname),
  }),
);
  • Next, use the x-eov-operation-id OpenAPI vendor extension or operationId to specify the id of operation handler to invoke.
/ping:
  get:
    # operationId: ping
    x-eov-operation-id: ping
  • Next, use the x-eov-operation-handler OpenAPI vendor extension to specify a path (relative to operationHandlers) to the module that contains the handler for this operation.
/ping:
  get:
    x-eov-operation-id: ping
    x-eov-operation-handler: routes/ping # no .js or .ts extension
  • Finally, create the express handler module e.g. routes/ping.js
module.exports = {
  // the express handler implementaiton for ping
  ping: (req, res) => res.status(200).send('pong'),
};

Note: A file may contain one or many handlers.

Below are some code snippets:

app.js

const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const logger = require('morgan');
const http = require('http');
const OpenApiValidator = require('express-openapi-validator');

const port = 3000;
const app = express();
const apiSpec = path.join(__dirname, 'api.yaml');

// 1. Install bodyParsers for the request types your API will support
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.text());
app.use(bodyParser.json());

app.use(logger('dev'));

app.use('/spec', express.static(apiSpec));

//  2. Install the OpenApiValidator on your express app
app.use(
  OpenApiValidator.middleware({
    apiSpec,
    validateResponses: true, // default false
    // 3. Provide the base path to the operation handlers directory
    operationHandlers: path.join(__dirname), // default false
  }),
);

// 4. Woah sweet! With auto-wired operation handlers, I don't have to declare my routes!
//    See api.yaml for x-eov-* vendor extensions

// 5. Create a custom error handler
app.use((err, req, res, next) => {
  // format errors
  res.status(err.status || 500).json({
    message: err.message,
    errors: err.errors,
  });
});

http.createServer(app).listen(port);
console.log(`Listening on port ${port}`);

module.exports = app;

api.yaml

/ping:
  get:
    description: |
      ping then pong!
    # OpenAPI's operationId may be used to to specify the operation id
    operationId: ping
    # x-eov-operation-id may be used to specify the operation id
    # Used when operationId is omiited. Overrides operationId when both are specified
    x-eov-operation-id: ping
    # specifies the path to the operation handler.
    # the path is relative to the operationHandlers option
    # e.g. operations/base/path/routes/ping.js
    x-eov-operation-handler: routes/ping
    responses:
      '200':
        description: OK
        # ...

ping.js

module.exports = {
  // ping must match operationId or x-eov-operation-id above
  // note that x-eov-operation-id overrides operationId
  ping: (req, res) => res.status(200).send('pong'),
};

API Validation Response Examples

Validates a query parameter with a value constraint

curl -s http://localhost:3000/v1/pets/as |jq
{
  "message": "request.params.id should be integer",
  "errors": [
    {
      "path": ".params.id",
      "message": "should be integer",
      "errorCode": "type.openapi.validation"
    }
  ]
}

Validates a query parameter with a range constraint

 curl -s 'http://localhost:3000/v1/pets?limit=25' |jq
{
  "message": "request.query should have required property 'type', request.query.limit should be <= 20",
  "errors": [
    {
      "path": ".query.type",
      "message": "should have required property 'type'",
      "errorCode": "required.openapi.validation"
    },
    {
      "path": ".query.limit",
      "message": "should be <= 20",
      "errorCode": "maximum.openapi.validation"
    }
  ]
}

Validates securities e.g. API Key

 curl -s --request POST \
  --url http://localhost:3000/v1/pets \
  --data '{}' |jq
{
  "message": "'X-API-Key' header required",
  "errors": [
    {
      "path": "/v1/pets",
      "message": "'X-API-Key' header required"
    }
  ]
}

Providing the header passes OpenAPI validation.

Note: that your Express middleware or endpoint logic can then provide additional checks.

curl -XPOST http://localhost:3000/v1/pets \
  --header 'X-Api-Key: XXXXX' \
  --header 'content-type: application/json' \
  -d '{"name": "spot"}' | jq

{
  "id": 4,
  "name": "spot"
}

Validates content-type

curl -s --request POST \
  --url http://localhost:3000/v1/pets \
  --header 'content-type: application/xml' \
  --header 'x-api-key: XXXX' \
  --data '{
        "name": "test"
}' |jq
  "message": "unsupported media type application/xml",
  "errors": [
    {
      "path": "/v1/pets",
      "message": "unsupported media type application/xml"
    }
  ]
}

Validates a POST request body

curl -s --request POST \
  --url http://localhost:3000/v1/pets \
  --header 'content-type: application/json' \
  --header 'x-api-key: XXXX' \
  --data '{}'|jq
{
  "message": "request.body should have required property 'name'",
  "errors": [
    {
      "path": ".body.name",
      "message": "should have required property 'name'",
      "errorCode": "required.openapi.validation"
    }
  ]
}

File Upload (out of the box)

curl -XPOST http://localhost:3000/v1/pets/10/photos -F file=@app.js|jq
{
  "files_metadata": [
    {
      "originalname": "app.js",
      "encoding": "7bit",
      "mimetype": "application/octet-stream"
    }
  ]
}

Validates responses (optional)

Errors in response validation return 500, not of 400

/v1/pets/99 will return a response that does not match the spec

 curl -s 'http://localhost:3000/v1/pets/99' |jq
{
  "message": ".response should have required property 'name', .response should have required property 'id'",
  "errors": [
    {
      "path": ".response.name",
      "message": "should have required property 'name'",
      "errorCode": "required.openapi.validation"
    },
    {
      "path": ".response.id",
      "message": "should have required property 'id'",
      "errorCode": "required.openapi.validation"
    }
  ]
}

…and much more. Try it out!

Response status codes

express-openapi-validator returns the following error codes depending on the situation.

Request validation (validateRequests=true)

statuswhen
400 (bad request)a validation error is encountered
401 (unauthorized)a security / authentication errors is encountered e.g. missing api-key, Authorization header, etc
404 (not found)a path is not found i.e. not declared in the API spec
405 (method not allowed)a path is declared in the API spec, but a no schema is provided for the method

Response validation (validateResponses=true)

statuswhen
500 (internal server error)any error is encountered by the validator

Advanced Usage

OpenApiValidator Middleware Options

express-openapi validator provides a good deal of flexibility via its options.

Options are provided via the options object. Options take the following form:

OpenApiValidator.middleware({
  apiSpec: './openapi.yaml',
  validateRequests: true,
  validateResponses: true,
  validateSecurity: {
    handlers: {
      ApiKeyAuth: (req, scopes, schema) => {
        throw { status: 401, message: 'sorry' }
      }
    }
  },
  validateFormats: 'fast',
  formats: [{
    name: 'my-custom-format',
    type: 'string' | 'number',
    validate: (value: any) => boolean,
  }],
  unknownFormats: ['phone-number', 'uuid'],
  operationHandlers: false | 'operations/base/path' | { ... },
  ignorePaths: /.*\/pets$/,
  fileUploader: { ... } | true | false,
  $refParser: {
    mode: 'bundle'
  }
});

▪️ apiSpec (required)

Specifies the path to an OpenAPI 3 specification or a JSON object representing the OpenAPI 3 specificiation

apiSpec: './path/to/my-openapi-spec.yaml';

or

  apiSpec: {
  openapi: '3.0.1',
  info: {...},
  servers: [...],
  paths: {...},
  components: {
    responses: {...},
    schemas: {...}
  }
}

▪️ validateRequests (optional)

Determines whether the validator should validate requests.

  • true (default) - validate requests.

  • false - do not validate requests.

  • { ... } - validate requests with options

    allowUnknownQueryParameters:

  • true - enables unknown/undeclared query parameters to pass validation

  • false - (default) fail validation if an unknown query parameter is present

    For example:

  validateRequests: {
    allowUnknownQueryParameters: true;
  }

allowUnknownQueryParameters is set for the entire validator. It can be overwritten per-operation using a custom property x-allow-unknown-query-parameters.

For example to allow unknown query parameters on ONLY a single endpoint:

  paths:
    /allow_unknown:
      get:
        x-allow-unknown-query-parameters: true
        parameters:
          - name: value
            in: query
            schema:
              type: string
        responses:
          200:
            description: success

coerceTypes:

Determines whether the validator will coerce the request body. Request query and path params, headers, cookies are coerced by default and this setting does not affect that.

Options:

  • true - coerce scalar data types.

  • false - (default) do not coerce types. (more strict, safer)

  • "array" - in addition to coercions between scalar types, coerce scalar data to an array with one element and vice versa (as required by the schema).

    For example:

  validateRequests: {
    coerceTypes: true;
  }

▪️ validateResponses (optional)

Determines whether the validator should validate responses. Also accepts response validation options.

  • true - validate responses in 'strict' mode i.e. responses MUST match the schema.

  • false (default) - do not validate responses

  • { ... } - validate responses with options

    removeAdditional:

  • "failing" - additional properties that fail schema validation are automatically removed from the response.

    coerceTypes:

  • true - coerce scalar data types.

  • false - (default) do not coerce types. (almost always the desired behavior)

  • "array" - in addition to coercions between scalar types, coerce scalar data to an array with one element and vice versa (as required by the schema).

    For example:

  validateResponses: {
    removeAdditional: 'failing';
  }

onError:

A function that will be invoked on response validation error, instead of the default handling. Useful if you want to log an error or emit a metric, but don't want to actually fail the request. Receives the validation error and offending response body.

For example:

  validateResponses: {
    onError: (error, body) => {
      console.log(`Response body fails validation: `, error);
      console.debug(body);
    }
  }

▪️ validateSecurity (optional)

Determines whether the validator should validate securities e.g. apikey, basic, oauth2, openid, etc

  • true (default) - validate security

  • false - do not validate security

  • { ... } - validate security with handlers. See Security handlers doc.

    handlers:

    For example:

  validateSecurity: {
    handlers: {
      ApiKeyAuth: function(req, scopes, schema) {
        console.log('apikey handler throws custom error', scopes, schema);
        throw Error('my message');
      },
    }
  }

▪️ formats (optional)

Defines a list of custome formats.

  • [{ ... }] - array of custom format objects. Each object must have the following properties:
  • name: string (required) - the format name
  • validate: (v: any) => boolean (required) - the validation function
  • type: 'string' | 'number' (optional) - the format's type

e.g.

formats: [
  {
    name: 'my-three-digit-format',
    type: 'number',
    // validate returns true the number has 3 digits, false otherwise
    validate: (v) => /^\d{3}$/.test(v.toString()),
  },
  {
    name: 'my-three-letter-format',
    type: 'string',
    // validate returns true the string has 3 letters, false otherwise
    validate: (v) => /^[A-Za-z]{3}$/.test(v),
  },
];

Then use it in a spec e.g.

my_property:
  type: string
  format: my-three-letter-format'

▪️ validateFormats (optional)

Specifies the strictness of validation of string formats.

  • "fast" (default) - only validate syntax, but not semantics. E.g. 2010-13-30T23:12:35Z will pass validation eventhough it contains month 13.
  • "full" - validate both syntax and semantics. Illegal dates will not pass.
  • false - do not validate formats at all.

▪️ unknownFormats (optional)

Defines how the validator should behave if an unknown or custom format is encountered.

  • true (default) - When an unknown format is encountered, the validator will report a 400 error.
  • [string] (recommended for unknown formats) - An array of unknown format names that will be ignored by the validator. This option can be used to allow usage of third party schemas with format(s), but still fail if another unknown format is used. e.g.
  unknownFormats: ['phone-number', 'uuid'];
  • "ignore" - to log warning during schema compilation and always pass validation. This option is not recommended, as it allows to mistype format name and it won't be validated without any error message.

▪️ operationHandlers (optional)

Defines the base directory for operation handlers. This is used in conjunction with express-openapi-validator's OpenAPI vendor extensions, x-eov-operation-id, x-eov-operation-handler and OpenAPI's operationId. See example.

Additionally, if you want to change how modules are resolved e.g. use dot deliminted operation ids e.g. path.to.module.myFunction, you may optionally add a custom resolver. See documentation and example

  • string - the base directory containing operation handlers

  • false - (default) disable auto wired operation handlers

  • { ... } - specifies a base directory and optionally a custom resolver

    handlers:

    For example:

  operationHandlers: {
    basePath: __dirname,
    resolver: function (modulePath, route): express.RequestHandler {
      ///...
    }
  }
operationHandlers: 'operations/base/path'

Note that the x-eov-operation-handler OpenAPI vendor extension specifies a path relative to operationHandlers. Thus if operationHandlers is /handlers and an x-eov-operation-handler has path routes/ping, then the handler file /handlers/routes/ping.js (or ts) is used.

Complete example here

api.yaml

/ping:
  get:
    description: |
      ping then pong!
    # OpenAPI's operationId may be used to to specify the operation id
    operationId: ping
    # x-eov-operation-id may be used to specify the operation id
    # Used when operationId is omiited. Overrides operationId when both are specified
    x-eov-operation-id: ping
    # specifies the path to the operation handler.
    # the path is relative to the operationHandlers option
    # e.g. operations/base/path/routes/ping.js
    x-eov-operation-handler: routes/ping
    responses:
      '200':
        description: OK
        # ...

routes/ping.js

x-eov-operation-handler specifies the path to this handlers file, ping.js

x-eov-operation-id (or operationId) specifies operation handler's key e.g. ping

module.exports = {
  ping: (req, res) => res.status(200).send('pong'),
};

▪️ ignorePaths (optional)

Defines a regular expression or function that determines whether a path(s) should be ignored. If it's a regular expression, any path that matches the regular expression will be ignored by the validator. If it's a function, it will ignore any paths that returns a truthy value.

The following ignores any path that ends in /pets e.g. /v1/pets. As a regular expression:

ignorePaths: /.*\/pets$/

or as a function:

ignorePaths: (path) => path.endsWith('/pets')

▪️ fileUploader (optional)

Specifies the options to passthrough to multer. express-openapi-validator uses multer to handle file uploads. see multer opts

  • true (default) - enables multer and provides simple file(s) upload capabilities

  • false - disables file upload capability. Upload capabilities may be provided by the user

  • {...} - multer options to be passed-through to multer. see multer opts for possible options

    e.g.

  fileUploader: {
    dest: 'uploads/';
  }

▪️ \$refParser.mode (optional)

Determines how JSON schema references are resolved by the internal json-schema-ref-parser. Generally, the default mode, bundle is sufficient, however if you use escape characters in \$refs, dereference is necessary.

  • bundle (default) - Bundles all referenced files/URLs into a single schema that only has internal $ref pointers. This eliminates the risk of circular references, but does not handle escaped characters in $refs.
  • dereference - Dereferences all $ref pointers in the JSON Schema, replacing each reference with its resolved value. Introduces risk of circular $refs. Handles escape characters in \$refs)

See this issue for more information.

e.g.

$refParser: {
  mode: 'bundle';
}

▪️ coerceTypes (optional) - deprecated

Determines whether the validator should coerce value types to match the those defined in the OpenAPI spec. This option applies only to path params, query strings, headers, and cookies. It is highly unlikley that will want to disable this. As such this option is deprecated and will be removed in the next major version

  • true (default) - coerce scalar data types.
  • "array" - in addition to coercions between scalar types, coerce scalar data to an array with one element and vice versa (as required by the schema).

The Base URL

The validator will only validate requests, securities, and responses that are under the server's base URL.

This is useful for those times when the API and frontend are being served by the same application. (More detail about the base URL.)

servers:
  - url: https://api.example.com/v1

The validation applies to all paths defined under this base URL. Routes in your app that are _not_se URL—such as pages—will not be validated.

URLValidated?
https://api.example.com/v1/users:whitecheckmark:
https://api.example.com/index.htmlno; not under the base URL

In some cases, it may be necessary to skip validation for paths under the base url. To do this, use the ignorePaths option.

Security handlers

Note: security handlers are an optional component. security handlers provide a convenience, whereby the request, declared scopes, and the security schema itself are provided as parameters to each security handlers callback that you define. The code you write in each callback can then perform authentication and authorization checks. Note that the same can be achieved using standard Express middleware. The difference is that security handlers provide you the OpenAPI schema data described in your specification_. Ulimately, this means, you don't have to duplicate that information in your code.

All in all, security handlers are purely optional and are provided as a convenience.

Security handlers specify a set of custom security handlers to be used to validate security i.e. authentication and authorization. If a security handlers object is specified, a handler must be defined for all securities. If security `handlers are not specified, a default handler is always used. The default handler will validate against the OpenAPI spec, then call the next middleware.

If security handlers are specified, the validator will validate against the OpenAPI spec, then call the security handler providing it the Express request, the security scopes, and the security schema object.

  • security handlers is an object that maps security keys to security handler functions. Each security key must correspond to securityScheme name. The validateSecurity.handlers object signature is as follows:
  {
    validateSecurity: {
      handlers: {
        [securityKey]: function(
          req: Express.Request,
          scopes: string[],
          schema: SecuritySchemeObject
        ): void,
      }
    }
  }

SecuritySchemeObject

For example:

  validateSecurity: {
    handlers: {
      ApiKeyAuth: function(req, scopes, schema) {
        console.log('apikey handler throws custom error', scopes, schema);
        throw Error('my message');
      },
    }
  }

The express-openapi-validator performs a basic validation pass prior to delegating to security handlers. If basic validation passes, security handler function(s) are invoked.

In order to signal an auth failure, the security handler function must either:

  1. throw { status: 403, message: 'forbidden' }
  2. throw Error('optional message')
  3. return false
  4. return a promise which resolves to false e.g Promise.resolve(false)
  5. return a promise rejection e.g.
  • Promise.reject({ status: 401, message: 'yikes' });
  • Promise.reject(Error('optional 'message')
  • Promise.reject(false)

Note: error status 401 is returned, unless option i. above is used

Some examples:

validateSecurity: {
  handlers: {
    ApiKeyAuth: (req, scopes, schema) => {
      throw Error('my message');
    },
    OpenID: async (req, scopes, schema) => {
      throw { status: 403, message: 'forbidden' }
    },
    BasicAuth: (req, scopes, schema) => {
      return Promise.resolve(false);
    },
    ...
  }
}

In order to grant authz, the handler function must either:

  • return true
  • return a promise which resolves to true

Some examples

validateSecurity: {
  handlers: {
    ApiKeyAuth: (req, scopes, schema) => {
      return true;
    },
    BearerAuth: async (req, scopes, schema) => {
      return true;
    },
    ...
  }
}

Each security handlers' securityKey must match a components/securitySchemes property

components:
  securitySchemes:
    ApiKeyAuth: # <-- Note this name must be used as the name handler function property
      type: apiKey
      in: header
      name: X-API-Key

See OpenAPI 3 authentication for securityScheme and security documentation See examples from unit tests

Example: Multiple Validators and API specs

It may be useful to serve multiple APIs with separate specs via single service. An example might be an API that serves both v1 and v2 from the same service. The sample code below shows how one might accomplish this.

See complete example

const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const http = require('http');
const OpenApiValidator = require('express-openapi-validator');

app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.text());
app.use(bodyParser.json());

const versions = [1, 2];

for (const v of versions) {
  const apiSpec = path.join(__dirname, `api.v${v}.yaml`);
  app.use(
    OpenApiValidator.middleware({
      apiSpec,
    }),
  );

  routes(app, v);
}

http.createServer(app).listen(3000);
console.log('Listening on port 3000');

function routes(app, v) {
  if (v === 1) routesV1(app);
  if (v === 2) routesV2(app);
}

function routesV1(app) {
  const v = '/v1';
  app.post(`${v}/pets`, (req, res, next) => {
    res.json({ ...req.body });
  });
  app.get(`${v}/pets`, (req, res, next) => {
    res.json([
      {
        id: 1,
        name: 'happy',
        type: 'cat',
      },
    ]);
  });

  app.use((err, req, res, next) => {
    // format error
    res.status(err.status || 500).json({
      message: err.message,
      errors: err.errors,
    });
  });
}

function routesV2(app) {
  const v = '/v2';
  app.get(`${v}/pets`, (req, res, next) => {
    res.json([
      {
        pet_id: 1,
        pet_name: 'happy',
        pet_type: 'kitty',
      },
    ]);
  });
  app.post(`${v}/pets`, (req, res, next) => {
    res.json({ ...req.body });
  });

  app.use((err, req, res, next) => {
    // format error
    res.status(err.status || 500).json({
      message: err.message,
      errors: err.errors,
    });
  });
}

module.exports = app;

FAQ

Q: How do I match paths, like those described in RFC-6570?

A: OpenAPI 3.0 does not support RFC-6570. That said, we provide a minimalistic mechanism that conforms syntactically to OpenAPI 3 and accomplishes a common use case. For example, matching file paths and storing the matched path in req.params

Using the following OpenAPI 3.x defintion

/files/{path}*:
  get:
    parameters:
      - name: path
        in: path
        required: true
        schema:
          type: string

With the following Express route defintion

  app.get(`/files/:path(*)`, (req, res) => { /* do stuff */ }`

A path like /files/some/long/path will pass validation. The Express req.params.path property will hold the value some/long/path.

Q: Can I use discriminators with oneOf and anyOf?

A: Currently, there is support for top level discriminators. See top-level discriminator example

Q: What happened to the securityHandlers property?

A: In v3, securityHandlers have been replaced by validateSecurity.handlers. To use v3 security handlers, move your existing security handlers to the new property. No other change is required. Note that the v2 securityHandlers property is supported in v3, but deprecated

Q: What happened to the multerOpts property?

A: In v3, multerOpts have been replaced by fileUploader. In order to use the v3 fileUploader, move your multer options to fileUploader No other change is required. Note that the v2 multerOpts property is supported in v3, but deprecated

Q: I can disallow unknown query parameters with allowUnknownQueryParameters: false. How can disallow unknown body parameters?

A: Add additionalProperties: false when describing e.g a requestBody to ensure that additional properties are not allowed. For example:

Pet:
additionalProperties: false
required:
  - name
properties:
  name:
    type: string
  type:
    type: string

Q: I upgraded from from v2 to v3 and validation no longer works. How do I fix it?

A: In version 2.x.x, the install method was executed synchronously, in 3.x it's executed asynchronously. To get v2 behavior in v3, use the installSync method. See the synchronous section for details.

Q: Can I use express-openapi-validator with swagger-ui-express?

A: Yes. Be sure to use the swagger-ui-express serve middleware prior to installing OpenApiValidator. This will ensure that swagger-ui-express is able to fully prepare the spec before before OpenApiValidator attempts to use it. For example:

const swaggerUi = require('swagger-ui-express')
const OpenApiValidator = require('express-openapi-validator')

...

app.use('/', swaggerUi.serve, swaggerUi.setup(documentation))

app.use(OpenApiValidator.middleware({
  apiSpec, // api spec JSON object
  //... other options
  }
}))

Q: I have a handler function defined on an express.Router. If i call req.params each param value has type string. If i define same handler function on an express.Application, each value in req.params is already coerced to the type declare in my spec. Why not coerce theseF values on an express.Router?

A: First, it's important to note that this behavior does not impact validation. The validator will validate against the type defined in your spec.

In order to modify the req.params, express requires that a param handler be registered e.g. app.param(...) or router.param(...). Since app is available to middleware functions, the validator registers an app.param handler to coerce and modify the values of req.params to their declared types. Unfortunately, express does not provide a means to determine the current router from a middleware function, hence the validator is unable to register the same param handler on an express router. Ultimately, this means if your handler function is defined on app, the values of req.params will be coerced to their declared types. If your handler function is declare on an express.Router, the values of req.params values will be of type string (You must coerce them e.g. parseInt(req.params.id)).

Contributors ✨

Contributions welcome! Here's how to contribute.

Thanks goes to these wonderful people (emoji key):


Carmine DiMascio

???? ⚠️ ????

Sheldhur Mornor

???? ⚠️

Andrey Trebler

???? ⚠️

richdouglasevans

????

Miran Setinc

????

Frank Calise

????

Gonen Dukas

???? ⚠️

Sven Eliasson

???? ⚠️

Spencer Brown

???? ⚠️

José Neves

????

mk811

???? ⚠️

HugoMario

???? ⚠️

Rowan Cockett

????

Jacques Yakoub

????

ckeboss

???? ⚠️

JacobLey

???? ⚠️

Dmitriy Voeykov

???? ⚠️

GARAMKIM

???? ????

Mathias Scherer

????

Mirek

????

Florian Beutel

????

jakubskopal

???? ⚠️ ????

Jordan Dobrev

⚠️ ????

Enrico Fabris

????

Dustin Wheeler

???? ???? ⚠️

Thomas Carmichael

????

Jakesterwars

????

xtrycatchx

????

Lee Dong Joo

????

Dmitry Chekanov

???? ⚠️

Redhart Azul

????

Joost Diepenmaat

???? ⚠️

Dom Parfitt

???? ⚠️

xg1990

????

ownagedj

???? ⚠️

David Garner

????

Balazs Soltesz

???? ⚠️

Christiaan Nieuwlaat

????

Ilya

???? ⚠️

Yuliya Bagriy

???? ⚠️

Kristjan Siimson

???? ⚠️

Guillaume

???? ⚠️

Volodymyr Kolesnykov

???? ⚠️

Pierre Le Roux

???? ⚠️ ????

Electro Type

????

This project follows the all-contributors specification. Contributions of any kind welcome!

Community articles, blogs, and tutorials

Seeking content creators…

Have you written an article, blog, or tutorial that uses express-openapi-validator?

Please post your links here

We plan to publicize a variety of links here.

License

MIT

Buy Me A Coffee

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文