generated from moontai0724/package-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(openapi): path define and document generation
- Loading branch information
1 parent
5a324f0
commit 44f2a89
Showing
8 changed files
with
373 additions
and
126 deletions.
There are no files selected for viewing
Empty file.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import type { OpenAPIObject, PathItemObject } from "@moontai0724/openapi-types"; | ||
import YAML from "json-to-pretty-yaml"; | ||
|
||
import { | ||
type HttpMethod, | ||
type OperationSchemas, | ||
transformPathItem, | ||
type TransformPathItemOptions, | ||
} from "../transformers"; | ||
import { deepMerge } from "../utils/deep-merge"; | ||
import { getOrInit } from "../utils/get-or-init"; | ||
|
||
export interface BasicOpenAPIObject extends Omit<OpenAPIObject, "paths"> {} | ||
|
||
export class OpenAPI { | ||
protected operationSchemas: Map<string, OperationSchemas> = new Map(); | ||
|
||
/** | ||
* Create a new instance to define and generate OpenAPI document. | ||
* @param document OpenAPI document initial value, will be merged with the result of `define` method | ||
* @param options Options for features about this instance | ||
*/ | ||
constructor(protected document: BasicOpenAPIObject) {} | ||
|
||
/** | ||
* Define an operation for a path, save original schemas and also transform the schemas into OpenAPI format. | ||
* @param path API endpoint path, used as key in paths object. | ||
* @param method HTTP method that this operation is for, will overwrite existing path item if it exists. | ||
* @param operationSchemas Schemas for this operation. | ||
* @param pathItemOptions Additional options to overwrite properties. | ||
* @returns Generated path item object. | ||
*/ | ||
public define( | ||
path: `/${string}`, | ||
method: HttpMethod, | ||
operationSchemas: OperationSchemas, | ||
options: TransformPathItemOptions = {}, | ||
): PathItemObject { | ||
this.operationSchemas.set( | ||
`${method.toUpperCase()} ${path}`, | ||
operationSchemas, | ||
); | ||
|
||
const paths = getOrInit(this.document as OpenAPIObject, "paths", {}); | ||
const existingPathItem = paths[path] ?? {}; | ||
const pathItemOptions: TransformPathItemOptions = deepMerge(options, { | ||
pathItem: existingPathItem, | ||
}); | ||
|
||
const pathItem = transformPathItem( | ||
method, | ||
operationSchemas, | ||
pathItemOptions, | ||
); | ||
|
||
paths[path] = pathItem; | ||
|
||
return pathItem; | ||
} | ||
|
||
/** | ||
* Stringify the document to JSON format. | ||
* @returns OpenAPI document string in JSON format. | ||
*/ | ||
public json(): string { | ||
return JSON.stringify(this.document); | ||
} | ||
|
||
/** | ||
* Stringify the document to YAML format. | ||
* @returns OpenAPI document string in YAML format. | ||
*/ | ||
public yaml(): string { | ||
return YAML.stringify(this.document); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import type { | ||
OpenAPIObject, | ||
OperationObject, | ||
} from "@moontai0724/openapi-types"; | ||
import { beforeAll, beforeEach, expect, it, vi } from "vitest"; | ||
|
||
const expectedOperation: OperationObject = { | ||
parameters: [], | ||
requestBody: { | ||
content: {}, | ||
}, | ||
responses: { | ||
"200": { | ||
description: "No Description.", | ||
}, | ||
}, | ||
}; | ||
|
||
const transformPathItem = vi.fn(); | ||
|
||
vi.mock("../../transformers", () => ({ | ||
transformPathItem, | ||
})); | ||
|
||
let OpenAPI: typeof import("..").OpenAPI; | ||
|
||
beforeAll(async () => { | ||
OpenAPI = await import("..").then((m) => m.OpenAPI); | ||
}); | ||
|
||
const baseOpenAPIDocument = { | ||
openapi: "3.1.0", | ||
info: { | ||
title: "Example API", | ||
version: "1.0.0", | ||
}, | ||
}; | ||
|
||
let openapi: InstanceType<typeof OpenAPI>; | ||
|
||
beforeEach(() => { | ||
openapi = new OpenAPI(baseOpenAPIDocument); | ||
}); | ||
|
||
it("should be able to transform empty schemas and pass correct schemas to transformers", () => { | ||
const path = "/"; | ||
const method = "patch"; | ||
|
||
transformPathItem.mockReturnValueOnce({ [method]: expectedOperation }); | ||
openapi.define(path, method, {}); | ||
|
||
const expected = { | ||
...baseOpenAPIDocument, | ||
paths: { [path]: { [method]: expectedOperation } }, | ||
} satisfies OpenAPIObject; | ||
|
||
expect(JSON.parse(openapi.json())).toEqual(expected); | ||
|
||
expect(transformPathItem).toHaveBeenCalledWith(method, {}, { pathItem: {} }); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import type { | ||
OpenAPIObject, | ||
OperationObject, | ||
} from "@moontai0724/openapi-types"; | ||
import { Type } from "@sinclair/typebox"; | ||
import { beforeAll, beforeEach, expect, it, vi } from "vitest"; | ||
|
||
import type { OperationSchemas } from "../../transformers"; | ||
|
||
const expectedOperation: OperationObject = { | ||
parameters: [], | ||
requestBody: { | ||
content: {}, | ||
}, | ||
responses: { | ||
"200": { | ||
description: "No Description.", | ||
}, | ||
}, | ||
}; | ||
|
||
const transformPathItem = vi.fn(); | ||
|
||
vi.mock("../../transformers", () => ({ | ||
transformPathItem, | ||
})); | ||
|
||
let OpenAPI: typeof import("..").OpenAPI; | ||
|
||
beforeAll(async () => { | ||
OpenAPI = await import("..").then((m) => m.OpenAPI); | ||
}); | ||
|
||
const baseOpenAPIDocument = { | ||
openapi: "3.1.0", | ||
info: { | ||
title: "Example API", | ||
version: "1.0.0", | ||
}, | ||
}; | ||
|
||
let openapi: InstanceType<typeof OpenAPI>; | ||
|
||
beforeEach(() => { | ||
openapi = new OpenAPI(baseOpenAPIDocument); | ||
}); | ||
|
||
it("should be able to transform schemas and pass correct schemas to transformers", () => { | ||
const path = "/"; | ||
const method = "patch"; | ||
const schemas: OperationSchemas = { | ||
body: Type.Object({ | ||
body1: Type.String(), | ||
}), | ||
cookie: Type.Object({ | ||
cookie1: Type.String(), | ||
}), | ||
header: Type.Object({ | ||
header1: Type.String(), | ||
}), | ||
path: Type.Object({ | ||
path1: Type.String(), | ||
}), | ||
query: Type.Object({ | ||
query1: Type.String(), | ||
}), | ||
response: Type.Object({ | ||
response1: Type.String(), | ||
}), | ||
}; | ||
|
||
transformPathItem.mockReturnValueOnce({ [method]: expectedOperation }); | ||
openapi.define(path, method, schemas); | ||
|
||
const expected = { | ||
...baseOpenAPIDocument, | ||
paths: { [path]: { [method]: expectedOperation } }, | ||
} satisfies OpenAPIObject; | ||
|
||
expect(JSON.parse(openapi.json())).toEqual(expected); | ||
|
||
expect(transformPathItem).toHaveBeenCalledWith(method, schemas, { | ||
pathItem: {}, | ||
}); | ||
}); |
Oops, something went wrong.