-
-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
1,238 additions
and
34 deletions.
There are no files selected for viewing
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,11 @@ | ||
# openapi-codegen | ||
|
||
This library was generated with [Nx](https://nx.dev). | ||
|
||
## Building | ||
|
||
Run `nx build openapi-codegen` to build the library. | ||
|
||
## Running unit tests | ||
|
||
Run `nx test openapi-codegen` to execute the unit tests via [Jest](https://jestjs.io). |
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,49 @@ | ||
{ | ||
"name": "@farfetched/openapi-codegen", | ||
"version": "0.0.1", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"license": "MIT", | ||
"repository": "https://github.com/igorkamyshev/farfetched", | ||
"dependencies": { | ||
"handlebars": "^4.7.8", | ||
"oas": "^24.1.0", | ||
"oas-normalize": "^11.0.1" | ||
}, | ||
"devDependencies": { | ||
"@farfetched/core": "workspace:*", | ||
"@farfetched/json-schema": "workspace:*", | ||
"tsx": "^4.6.2" | ||
}, | ||
"peerDependencies": { | ||
"@farfetched/core": "workspace:*", | ||
"@farfetched/json-schema": "workspace:*", | ||
"effector": "^23.0.0" | ||
}, | ||
"scripts": { | ||
"start": "tsx ./src/index.ts", | ||
"test:run": "vitest run --typecheck", | ||
"publint": "node ../../tools/scripts/publint.mjs", | ||
"typelint": "attw --pack" | ||
}, | ||
"type": "module", | ||
"files": [ | ||
"dist" | ||
], | ||
"main": "./dist/openapi-codegen.cjs", | ||
"module": "./dist/openapi-codegen.js", | ||
"types": "./dist/openapi-codegen.d.ts", | ||
"exports": { | ||
".": { | ||
"import": { | ||
"types": "./dist/openapi-codegen.d.ts", | ||
"default": "./dist/openapi-codegen.js" | ||
}, | ||
"require": { | ||
"types": "./dist/openapi-codegen.d.cts", | ||
"default": "./dist/openapi-codegen.cjs" | ||
} | ||
} | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
packages/openapi-codegen/src/__tests__/petstore/petstore.test.ts
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,14 @@ | ||
import { it, expect } from 'vitest'; | ||
|
||
import { generateEndpoints } from '../../index'; | ||
import path, { resolve } from 'path'; | ||
|
||
it('generates working code', async () => { | ||
const result = await generateEndpoints({ | ||
schemaFile: resolve(__dirname, './petstore.yaml'), | ||
apiFile: './test.ts', | ||
outputFile: './src/test.ts', | ||
}); | ||
|
||
expect(result).toMatchFileSnapshot('./petstore.ts'); | ||
}); |
65 changes: 65 additions & 0 deletions
65
packages/openapi-codegen/src/__tests__/petstore/petstore.ts
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,65 @@ | ||
import { createJsonQuery, createQuery, declareParams } from "@farfetched/core"; | ||
import { jsonSchemaContract } from "@farfetched/json-schema"; | ||
|
||
export const listPetsQuery = createJsonQuery({ | ||
params: declareParams<{ id: number }>(), | ||
request: { | ||
method: "GET", | ||
url: () => "/pets", | ||
}, | ||
response: { | ||
contract: jsonSchemaContract({ | ||
type: "array", | ||
maxItems: 100, | ||
items: { | ||
type: "object", | ||
required: ["id", "name"], | ||
properties: { | ||
id: { | ||
type: "integer", | ||
format: "int64", | ||
minimum: -9223372036854776000, | ||
maximum: 9223372036854776000, | ||
}, | ||
name: { type: "string" }, | ||
tag: { type: "string" }, | ||
}, | ||
}, | ||
$schema: "http://json-schema.org/draft-04/schema#", | ||
}), | ||
}, | ||
}); | ||
|
||
export const createPetsQuery = createJsonQuery({ | ||
params: declareParams<{ id: number }>(), | ||
request: { | ||
method: "POST", | ||
url: () => "/pets", | ||
}, | ||
response: {}, | ||
}); | ||
|
||
export const showPetByIdQuery = createJsonQuery({ | ||
params: declareParams<{ id: number }>(), | ||
request: { | ||
method: "GET", | ||
url: () => "/pets/{petId}", | ||
}, | ||
response: { | ||
contract: jsonSchemaContract({ | ||
type: "object", | ||
required: ["id", "name"], | ||
properties: { | ||
id: { | ||
type: "integer", | ||
format: "int64", | ||
minimum: -9223372036854776000, | ||
maximum: 9223372036854776000, | ||
}, | ||
name: { type: "string" }, | ||
tag: { type: "string" }, | ||
}, | ||
$schema: "http://json-schema.org/draft-04/schema#", | ||
}), | ||
}, | ||
}); |
113 changes: 113 additions & 0 deletions
113
packages/openapi-codegen/src/__tests__/petstore/petstore.yaml
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,113 @@ | ||
openapi: '3.0.0' | ||
info: | ||
version: 1.0.0 | ||
title: Swagger Petstore | ||
license: | ||
name: MIT | ||
servers: | ||
- url: http://petstore.swagger.io/v1 | ||
paths: | ||
/pets: | ||
get: | ||
summary: List all pets | ||
operationId: listPets | ||
tags: | ||
- pets | ||
parameters: | ||
- name: limit | ||
in: query | ||
description: How many items to return at one time (max 100) | ||
required: false | ||
schema: | ||
type: integer | ||
maximum: 100 | ||
format: int32 | ||
responses: | ||
'200': | ||
description: A paged array of pets | ||
headers: | ||
x-next: | ||
description: A link to the next page of responses | ||
schema: | ||
type: string | ||
content: | ||
application/json: | ||
schema: | ||
$ref: '#/components/schemas/Pets' | ||
default: | ||
description: unexpected error | ||
content: | ||
application/json: | ||
schema: | ||
$ref: '#/components/schemas/Error' | ||
post: | ||
summary: Create a pet | ||
operationId: createPets | ||
tags: | ||
- pets | ||
responses: | ||
'201': | ||
description: Null response | ||
default: | ||
description: unexpected error | ||
content: | ||
application/json: | ||
schema: | ||
$ref: '#/components/schemas/Error' | ||
/pets/{petId}: | ||
get: | ||
summary: Info for a specific pet | ||
operationId: showPetById | ||
tags: | ||
- pets | ||
parameters: | ||
- name: petId | ||
in: path | ||
required: true | ||
description: The id of the pet to retrieve | ||
schema: | ||
type: string | ||
responses: | ||
'200': | ||
description: Expected response to a valid request | ||
content: | ||
application/json: | ||
schema: | ||
$ref: '#/components/schemas/Pet' | ||
default: | ||
description: unexpected error | ||
content: | ||
application/json: | ||
schema: | ||
$ref: '#/components/schemas/Error' | ||
components: | ||
schemas: | ||
Pet: | ||
type: object | ||
required: | ||
- id | ||
- name | ||
properties: | ||
id: | ||
type: integer | ||
format: int64 | ||
name: | ||
type: string | ||
tag: | ||
type: string | ||
Pets: | ||
type: array | ||
maxItems: 100 | ||
items: | ||
$ref: '#/components/schemas/Pet' | ||
Error: | ||
type: object | ||
required: | ||
- code | ||
- message | ||
properties: | ||
code: | ||
type: integer | ||
format: int32 | ||
message: | ||
type: string |
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,34 @@ | ||
import OASNormalize from 'oas-normalize'; | ||
import * as prettier from 'prettier'; | ||
import Oas from 'oas'; | ||
import { generateQueries } from './lib/generator'; | ||
|
||
type GenerationOptions = { | ||
schemaFile: string; | ||
apiFile: string; | ||
outputFile: string; | ||
}; | ||
|
||
export async function generateEndpoints( | ||
options: GenerationOptions | ||
): Promise<string | void> { | ||
const oasNormalize = new OASNormalize(options.schemaFile, { | ||
enablePaths: true, | ||
}); | ||
|
||
const oasContent = await oasNormalize.validate(); | ||
|
||
const oas = new Oas(oasContent); | ||
|
||
await oas.dereference(); | ||
|
||
const sourceCode = generateQueries(oas); | ||
|
||
if (!sourceCode) { | ||
return; | ||
} | ||
|
||
const outputFile = options.outputFile; | ||
|
||
return prettier.format(sourceCode, { parser: 'typescript' }); | ||
} |
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,87 @@ | ||
import Oas from 'oas'; | ||
|
||
import { compile } from 'handlebars'; | ||
|
||
const apiGenerator = compile(` | ||
import { createJsonQuery, createQuery, declareParams } from '@farfetched/core'; | ||
import { jsonSchemaContract } from "@farfetched/json-schema"; | ||
{{#each queries}} | ||
{{{this}}} | ||
{{/each}} | ||
`); | ||
|
||
const queryGenerator = compile(` | ||
export const {{name}}Query = createJsonQuery({ | ||
params: declareParams<{ id: number }>(), | ||
request: { | ||
method: '{{method}}', | ||
url: () => '{{{url}}}', | ||
}, | ||
response: { | ||
{{{contract}}} | ||
}, | ||
}); | ||
`); | ||
|
||
const contractGenerator = compile( | ||
` contract: jsonSchemaContract( {{{jsonSchema}}} ),` | ||
); | ||
|
||
function getAllOasOperations(oas: Oas) { | ||
return Object.values(oas.getPaths()).flatMap((v) => Object.values(v)); | ||
} | ||
|
||
export function generateQueries(oas: Oas) { | ||
if (!oas.api.paths) { | ||
return; | ||
} | ||
|
||
const queries: string[] = []; | ||
|
||
const operations = getAllOasOperations(oas); | ||
|
||
for (const operation of operations) { | ||
const { method, path } = operation; | ||
|
||
if (!operation.isJson()) { | ||
console.warn(`Skipping ${method} ${path} because it is not JSON.`); | ||
|
||
continue; | ||
} | ||
|
||
const params = operation.getParametersAsJSONSchema(); | ||
|
||
const response = operation.getResponseAsJSONSchema(200) ?? []; | ||
|
||
const responseBody = response.find((v) => v.label === 'Response body'); | ||
|
||
if (responseBody) { | ||
deepOmit(responseBody, 'x-readme-ref-name'); | ||
} | ||
|
||
const ffQuery = queryGenerator({ | ||
name: operation.getOperationId(), | ||
url: path, | ||
method: method.toUpperCase(), | ||
contract: responseBody | ||
? contractGenerator({ jsonSchema: JSON.stringify(responseBody.schema) }) | ||
: '', | ||
}); | ||
|
||
queries.push(ffQuery); | ||
} | ||
|
||
return apiGenerator({ queries }); | ||
} | ||
|
||
function deepOmit(obj: Object, key: string) { | ||
// @ts-expect-error | ||
delete obj[key]; | ||
|
||
for (const val of Object.values(obj)) { | ||
if (typeof val === 'object') { | ||
deepOmit(val, key); | ||
} | ||
} | ||
} |
Oops, something went wrong.