適当にJSON Serverで作成したMockからOpenAPIの定義を生成できないか試したのでメモ。
結果としては一応生成できたが余分な値や足りない(クエリパラメータなど)部分がある。
ディレクトリ構成
./gen-openapi-from-json-server
|-- db.json
|-- package-lock.json
|-- package.json
`-- server.js
package.json
express-oas-generatorを使用して生成している。ただしそのままではschemaやexamplesが生成されないため、
localhostにリクエストしキャプチャして生成するために node-fetch を入れた。
package.json
{
"name": "gen-openapi-from-json-server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"gen": "node server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"express-oas-generator": "^1.0.45",
"json-server": "^0.17.0",
"node-fetch": "^2.6.7"
}
}
db.json
json-serverで使用しているdb.json
db.json
{
"pet": [
{
"id": 10,
"name": "doggie",
"category": {
"id": 1,
"name": "Dogs"
},
"photoUrls": [
"string"
],
"tags": [
{
"id": 0,
"name": "string"
}
],
"status": "available"
}
],
"user": [
{
"id": 10,
"username": "theUser",
"firstName": "John",
"lastName": "James",
"email": "[email protected]",
"password": "12345",
"phone": "12345",
"userStatus": 1
}
]
}
server.js
今回適当に作成した生成スクリプト。
express-oas-generatorを使用して生成しているが、pathしか生成されないのでキャプチャ機能を用いてdb.jsonの値をリクエストしてキャプチャしている。
クエリパラメータも試しにリクエストしてみたが、キャプチャされないためコメントアウト。
生成後はjson-serverは終了する。
server.js
const express = require("express");
const fs = require("fs");
const fetch = require("node-fetch");
const db = require("./db.json");
const BASE_URL = "http://localhost:3000";
const keys = Object.keys(db);
const postProcess = async (cb) => {
for (const key of keys) {
await pluralRoutesRequest(key, db[key][0]);
// queryパラメータはキャプチャしてくれない
// await filterRequest(key, value);
}
cb();
};
// Plural routes
const pluralRoutesRequest = async (path, data) => {
// GET /posts
let response = await fetch(`${BASE_URL}/${path}`);
// GET /posts/1
response = await fetch(`${BASE_URL}/${path}/${data.id}`);
// PUT /posts/1
response = await fetch(`${BASE_URL}/${path}/${data.id}`, {
method: "PUT",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
// PATCH /posts/1
response = await fetch(`${BASE_URL}/${path}/${data.id}`, {
method: "PATCH",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
// DELETE /posts/1
response = await fetch(`${BASE_URL}/${path}/${data.id}`, {
method: "DELETE",
});
// POST /posts
response = await fetch(`${BASE_URL}/${path}`, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
};
// Filter
const filterRequest = async (path, list) => {
let query = Object.entries(list[0]).reduce((previous, [key, value]) => {
if (key === "id") {
return previous;
}
previous = previous ? `${previous}&` : previous;
return `${previous}${key}=${value}`;
}, "");
// GET /posts?title=json-server&author=typicode
let response = await fetch(`${BASE_URL}/${path}?${query}`);
// GET /posts?id=1&id=2
response = await fetch(
`${BASE_URL}/${path}/?id=${list[0].id}&id=id=${list[1].id}`
);
};
const expressOasGenerator = require("express-oas-generator");
const jsonServer = require("json-server");
const server = jsonServer.create();
server.use(express.json()); // for parsing application/json
server.use(express.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
// expressOasGenerator.init(server, {});
expressOasGenerator.handleResponses(server, {
swaggerUiServePath: "api-docs",
specOutputPath: "./openapi.json",
predefinedSpec: {},
writeIntervalMs: 60 * 1000,
// mongooseModels: ["User", "Student"],
mongooseModels: [],
tags: keys,
ignoredNodeEnvironments: ["production"],
alwaysServeDocs: true,
specOutputFileBehavior: "PRESERVE",
// specOutputFileBehavior: "RECREATE",
swaggerDocumentOptions: {},
});
const router = jsonServer.router("db.json");
const middlewares = jsonServer.defaults();
server.use(middlewares);
server.use(router);
expressOasGenerator.handleRequests();
const s = server.listen(3000, () => {
// console.info("JSON Server is running");
postProcess(() => {
expressOasGenerator.getSpecV3((err, spec) => {
// console.log(JSON.stringify(spec));
const paths = spec.paths;
delete paths["/db"];
delete paths["/{resource}/{id}/{nested}"];
fs.writeFileSync("my-openapi_v3.json", JSON.stringify(spec));
s.close(() => {
console.info("Done.");
});
});
});
});
結果
my-openapi_v3.json
{
"openapi": "3.0.0",
"info": {
"title": "workspace",
"version": "1.0.0",
"license": { "name": "ISC" },
"description": "Specification JSONs: [v2](/api-spec/v2), [v3](/api-spec/v3)."
},
"paths": {
"/pet": {
"get": {
"summary": "/pet",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "number" },
"name": { "type": "string" },
"category": {
"type": "object",
"properties": {
"id": { "type": "number" },
"name": { "type": "string" }
}
},
"photoUrls": {
"type": "array",
"items": { "type": "string" }
},
"tags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "number" },
"name": { "type": "string" }
}
}
},
"status": { "type": "string" }
}
}
}
}
}
}
},
"tags": ["pet"]
},
"post": {
"summary": "/pet",
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": { "type": "number", "example": 10 },
"name": { "type": "string", "example": "doggie" },
"category": {
"type": "object",
"properties": {
"id": { "type": "number", "example": 1 },
"name": { "type": "string", "example": "Dogs" }
}
},
"photoUrls": {
"type": "array",
"items": { "type": "string" },
"example": ["string"]
},
"tags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "number" },
"name": { "type": "string" }
}
},
"example": [{ "id": 0, "name": "string" }]
},
"status": { "type": "string", "example": "available" }
}
}
}
}
}
},
"tags": ["pet"]
}
},
"/pet/{id}": {
"get": {
"summary": "/pet/{id}",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": { "type": "number", "example": 10 },
"name": { "type": "string", "example": "doggie" },
"category": {
"type": "object",
"properties": {
"id": { "type": "number", "example": 1 },
"name": { "type": "string", "example": "Dogs" }
}
},
"photoUrls": {
"type": "array",
"items": { "type": "string" },
"example": ["string"]
},
"tags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "number" },
"name": { "type": "string" }
}
},
"example": [{ "id": 0, "name": "string" }]
},
"status": { "type": "string", "example": "available" }
}
}
}
}
}
},
"tags": ["pet"]
},
"put": {
"summary": "/pet/{id}",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": { "type": "number", "example": 10 },
"name": { "type": "string", "example": "doggie" },
"category": {
"type": "object",
"properties": {
"id": { "type": "number", "example": 1 },
"name": { "type": "string", "example": "Dogs" }
}
},
"photoUrls": {
"type": "array",
"items": { "type": "string" },
"example": ["string"]
},
"tags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "number" },
"name": { "type": "string" }
}
},
"example": [{ "id": 0, "name": "string" }]
},
"status": { "type": "string", "example": "available" }
}
}
}
}
}
},
"tags": ["pet"]
},
"patch": {
"summary": "/pet/{id}",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": { "type": "number", "example": 10 },
"name": { "type": "string", "example": "doggie" },
"category": {
"type": "object",
"properties": {
"id": { "type": "number", "example": 1 },
"name": { "type": "string", "example": "Dogs" }
}
},
"photoUrls": {
"type": "array",
"items": { "type": "string" },
"example": ["string"]
},
"tags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "number" },
"name": { "type": "string" }
}
},
"example": [{ "id": 0, "name": "string" }]
},
"status": { "type": "string", "example": "available" }
}
}
}
}
}
},
"tags": ["pet"]
},
"delete": {
"summary": "/pet/{id}",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": { "type": "object", "properties": {} }
}
}
}
},
"tags": ["pet"]
}
},
"/user": {
"get": {
"summary": "/user",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "number" },
"username": { "type": "string" },
"firstName": { "type": "string" },
"lastName": { "type": "string" },
"email": { "type": "string" },
"password": { "type": "string" },
"phone": { "type": "string" },
"userStatus": { "type": "number" }
}
}
}
}
}
}
},
"tags": ["user"]
},
"post": {
"summary": "/user",
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": { "type": "number", "example": 10 },
"username": { "type": "string", "example": "theUser" },
"firstName": { "type": "string", "example": "John" },
"lastName": { "type": "string", "example": "James" },
"email": { "type": "string", "example": "[email protected]" },
"password": { "type": "string", "example": "******" },
"phone": { "type": "string", "example": "12345" },
"userStatus": { "type": "number", "example": 1 }
}
}
}
}
}
},
"tags": ["user"]
}
},
"/user/{id}": {
"get": {
"summary": "/user/{id}",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": { "type": "number", "example": 10 },
"username": { "type": "string", "example": "theUser" },
"firstName": { "type": "string", "example": "John" },
"lastName": { "type": "string", "example": "James" },
"email": { "type": "string", "example": "[email protected]" },
"password": { "type": "string", "example": "******" },
"phone": { "type": "string", "example": "12345" },
"userStatus": { "type": "number", "example": 1 }
}
}
}
}
}
},
"tags": ["user"]
},
"put": {
"summary": "/user/{id}",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": { "type": "number", "example": 10 },
"username": { "type": "string", "example": "theUser" },
"firstName": { "type": "string", "example": "John" },
"lastName": { "type": "string", "example": "James" },
"email": { "type": "string", "example": "[email protected]" },
"password": { "type": "string", "example": "******" },
"phone": { "type": "string", "example": "12345" },
"userStatus": { "type": "number", "example": 1 }
}
}
}
}
}
},
"tags": ["user"]
},
"patch": {
"summary": "/user/{id}",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": { "type": "number", "example": 10 },
"username": { "type": "string", "example": "theUser" },
"firstName": { "type": "string", "example": "John" },
"lastName": { "type": "string", "example": "James" },
"email": { "type": "string", "example": "[email protected]" },
"password": { "type": "string", "example": "******" },
"phone": { "type": "string", "example": "12345" },
"userStatus": { "type": "number", "example": 1 }
}
}
}
}
}
},
"tags": ["user"]
},
"delete": {
"summary": "/user/{id}",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": { "type": "object", "properties": {} }
}
}
}
},
"tags": ["user"]
}
}
},
"tags": [{ "name": "pet" }, { "name": "user" }]
}