mirror of
https://github.com/LukeHagar/redocly-cli.git
synced 2025-12-10 04:21:20 +00:00
feat: support for webhooks to the split command (#550)
Co-authored-by: Anton_Kozachuk <anton.kozachuk@cloudmobilizd.com> Co-authored-by: romanhotsiy <gotsijroman@gmail.com>
This commit is contained in:
@@ -26,7 +26,7 @@ function getEntrypoints(folderPath: string) {
|
|||||||
return Object.keys(redoclyYaml.apiDefinitions);
|
return Object.keys(redoclyYaml.apiDefinitions);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBundleResult(params: string[], folderPath: string) {
|
function getCommandOutput(params: string[], folderPath: string) {
|
||||||
const result = spawnSync('ts-node', params, {
|
const result = spawnSync('ts-node', params, {
|
||||||
cwd: folderPath,
|
cwd: folderPath,
|
||||||
env: {
|
env: {
|
||||||
@@ -72,6 +72,47 @@ describe('E2E', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('split', () => {
|
||||||
|
test('without option: outDir', () => {
|
||||||
|
const folderPath = join(__dirname, `split/missing-outDir`);
|
||||||
|
const args = [
|
||||||
|
'--transpile-only',
|
||||||
|
'../../../packages/cli/src/index.ts',
|
||||||
|
'split',
|
||||||
|
'../../../__tests__/split/test-split/spec.json',
|
||||||
|
];
|
||||||
|
const result = getCommandOutput(args, folderPath);
|
||||||
|
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, 'snapshot.js'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('swagger', () => {
|
||||||
|
const folderPath = join(__dirname, `split/oas2`);
|
||||||
|
const args = [
|
||||||
|
'--transpile-only',
|
||||||
|
'../../../packages/cli/src/index.ts',
|
||||||
|
'split',
|
||||||
|
'../../../__tests__/split/oas2/openapi.yaml',
|
||||||
|
'--outDir=output'
|
||||||
|
];
|
||||||
|
const result = getCommandOutput(args, folderPath);
|
||||||
|
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, 'snapshot.js'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('openapi with no errors', () => {
|
||||||
|
const folderPath = join(__dirname, `split/oas3-no-errors`);
|
||||||
|
const file = '../../../__tests__/split/oas3-no-errors/openapi.yaml';
|
||||||
|
const args = [
|
||||||
|
'--transpile-only',
|
||||||
|
'../../../packages/cli/src/index.ts',
|
||||||
|
'split',
|
||||||
|
file,
|
||||||
|
'--outDir=output'
|
||||||
|
];
|
||||||
|
const result = getCommandOutput(args, folderPath);
|
||||||
|
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, 'snapshot.js'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('join', () => {
|
describe('join', () => {
|
||||||
const folderPath = join(__dirname, 'join');
|
const folderPath = join(__dirname, 'join');
|
||||||
const contents = readdirSync(folderPath);
|
const contents = readdirSync(folderPath);
|
||||||
@@ -171,14 +212,14 @@ describe('E2E', () => {
|
|||||||
|
|
||||||
test.each(['codeframe','stylish','json','checkstyle'])('bundle lint: should be formatted by format: %s', (format) => {
|
test.each(['codeframe','stylish','json','checkstyle'])('bundle lint: should be formatted by format: %s', (format) => {
|
||||||
const params = [...args, `--format=${format}`];
|
const params = [...args, `--format=${format}`];
|
||||||
const result = getBundleResult(params, folderPath);
|
const result = getCommandOutput(params, folderPath);
|
||||||
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, `${format}-format-snapshot.js`));
|
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, `${format}-format-snapshot.js`));
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each(['noFormatParameter','emptyFormatValue'])('bundle lint: no format parameter or empty value should be formatted as codeframe', (format) => {
|
test.each(['noFormatParameter','emptyFormatValue'])('bundle lint: no format parameter or empty value should be formatted as codeframe', (format) => {
|
||||||
const formatArgument = format === 'emptyFormatValue' ? ['--format'] : [];
|
const formatArgument = format === 'emptyFormatValue' ? ['--format'] : [];
|
||||||
const params = [...args, ... formatArgument];
|
const params = [...args, ... formatArgument];
|
||||||
const result = getBundleResult(params, folderPath);
|
const result = getCommandOutput(params, folderPath);
|
||||||
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, `${format}-snapshot.js`));
|
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, `${format}-snapshot.js`));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -193,7 +234,7 @@ describe('E2E', () => {
|
|||||||
"--remove-unused-components",
|
"--remove-unused-components",
|
||||||
...entryPoints,
|
...entryPoints,
|
||||||
];
|
];
|
||||||
const result = getBundleResult(args, folderPath);
|
const result = getCommandOutput(args, folderPath);
|
||||||
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, 'remove-unused-components-snapshot.js'));
|
(<any>expect(result)).toMatchSpecificSnapshot(join(folderPath, 'remove-unused-components-snapshot.js'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
19
__tests__/split/missing-outDir/snapshot.js
Normal file
19
__tests__/split/missing-outDir/snapshot.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`E2E split without option: outDir 1`] = `
|
||||||
|
|
||||||
|
index.ts split [entrypoint]
|
||||||
|
|
||||||
|
Split definition into a multi-file structure.
|
||||||
|
|
||||||
|
Positionals:
|
||||||
|
entrypoint API definition file that you want to split [string] [required]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--version Show version number. [boolean]
|
||||||
|
--help Show help. [boolean]
|
||||||
|
--outDir Output directory where files will be saved. [string] [required]
|
||||||
|
|
||||||
|
Missing required argument: outDir
|
||||||
|
|
||||||
|
`;
|
||||||
3
__tests__/split/missing-outDir/spec.json
Normal file
3
__tests__/split/missing-outDir/spec.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.1"
|
||||||
|
}
|
||||||
45
__tests__/split/oas2/openapi.yaml
Normal file
45
__tests__/split/oas2/openapi.yaml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
swagger: "2.0"
|
||||||
|
info:
|
||||||
|
version: "1.0.0"
|
||||||
|
title: "Swagger Petstore"
|
||||||
|
description: "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification"
|
||||||
|
termsOfService: "http://swagger.io/terms/"
|
||||||
|
contact:
|
||||||
|
name: "Swagger API Team"
|
||||||
|
license:
|
||||||
|
name: "MIT"
|
||||||
|
host: "petstore.swagger.io"
|
||||||
|
basePath: "/api"
|
||||||
|
schemes:
|
||||||
|
- "http"
|
||||||
|
consumes:
|
||||||
|
- "application/json"
|
||||||
|
produces:
|
||||||
|
- "application/json"
|
||||||
|
paths:
|
||||||
|
/pets:
|
||||||
|
get:
|
||||||
|
description: "Returns all pets from the system that the user has access to"
|
||||||
|
produces:
|
||||||
|
- "application/json"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "A list of pets."
|
||||||
|
schema:
|
||||||
|
type: "array"
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/Pet"
|
||||||
|
definitions:
|
||||||
|
Pet:
|
||||||
|
type: "object"
|
||||||
|
required:
|
||||||
|
- "id"
|
||||||
|
- "name"
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: "integer"
|
||||||
|
format: "int64"
|
||||||
|
name:
|
||||||
|
type: "string"
|
||||||
|
tag:
|
||||||
|
type: "string"
|
||||||
8
__tests__/split/oas2/snapshot.js
Normal file
8
__tests__/split/oas2/snapshot.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`E2E split swagger 1`] = `
|
||||||
|
|
||||||
|
OpenAPI 2 is not supported by this command
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
111
__tests__/split/oas3-no-errors/openapi.yaml
Normal file
111
__tests__/split/oas3-no-errors/openapi.yaml
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
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
|
||||||
|
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
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Pet"
|
||||||
|
Error:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- code
|
||||||
|
- message
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
119
__tests__/split/oas3-no-errors/snapshot.js
Normal file
119
__tests__/split/oas3-no-errors/snapshot.js
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`E2E split openapi with no errors 1`] = `
|
||||||
|
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
tag:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: ./Pet.yaml
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- code
|
||||||
|
- message
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
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
|
||||||
|
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'
|
||||||
|
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'
|
||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
version: 1.0.0
|
||||||
|
title: Swagger Petstore
|
||||||
|
license:
|
||||||
|
name: MIT
|
||||||
|
servers:
|
||||||
|
- url: http://petstore.swagger.io/v1
|
||||||
|
paths:
|
||||||
|
/pets:
|
||||||
|
$ref: paths/pets.yaml
|
||||||
|
/pets/{petId}:
|
||||||
|
$ref: paths/pets@{petId}.yaml
|
||||||
|
🪓 Document: ../../../__tests__/split/oas3-no-errors/openapi.yaml is successfully split
|
||||||
|
and all related files are saved to the directory: output
|
||||||
|
|
||||||
|
../../../__tests__/split/oas3-no-errors/openapi.yaml: split processed in <test>ms
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
@@ -44,5 +44,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"x-webhooks": {
|
||||||
|
"test": {
|
||||||
|
"post": {
|
||||||
|
"summary": "New pet",
|
||||||
|
"description": "Information about a new pet in the systems",
|
||||||
|
"operationId": "newPet",
|
||||||
|
"tags": ["pet"],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": { "$ref": "#/components/schemas/Test" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Return a 200 status to indicate that the data was received successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {
|
||||||
|
"title": "Webhook Example",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/pets": {
|
||||||
|
"get": {
|
||||||
|
"summary": "List all pets",
|
||||||
|
"operationId": "listPets",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query",
|
||||||
|
"description": "How many items to return at one time (max 100)",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A paged array of pets",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Pets"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {
|
||||||
|
"test": {
|
||||||
|
"post": {
|
||||||
|
"requestBody": {
|
||||||
|
"description": "Information about a new pet in the system",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Pet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Return a 200 status to indicate that the data was received successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"Pet": {
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Pets": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Pet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +1,76 @@
|
|||||||
import { iteratePaths } from '../index';
|
import { iteratePathItems, handleSplit } from '../index';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as openapiCore from '@redocly/openapi-core';
|
import * as openapiCore from '@redocly/openapi-core';
|
||||||
|
import {
|
||||||
|
ComponentsFiles,
|
||||||
|
} from '../types';
|
||||||
|
import { blue, green } from 'colorette';
|
||||||
|
|
||||||
jest.mock('../../../utils', () => ({
|
jest.mock('../../../utils', () => ({
|
||||||
...jest.requireActual('../../../utils'),
|
...jest.requireActual('../../../utils'),
|
||||||
writeYaml: jest.fn(),
|
writeYaml: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('#split', () => {
|
jest.mock('@redocly/openapi-core', () => ({
|
||||||
it('should have correct paths for mac', () => {
|
...jest.requireActual('@redocly/openapi-core'),
|
||||||
const pathsDir = 'test/paths';
|
isRef: jest.fn(),
|
||||||
const openapiDir = 'test';
|
}));
|
||||||
|
|
||||||
|
describe('#split', () => {
|
||||||
|
const openapiDir = 'test';
|
||||||
|
const componentsFiles: ComponentsFiles = {};
|
||||||
|
|
||||||
|
it('should split the file and show the success message', async () => {
|
||||||
|
const filePath = "./packages/cli/src/commands/split/__tests__/fixtures/spec.json";
|
||||||
|
jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
||||||
|
|
||||||
|
await handleSplit (
|
||||||
|
{
|
||||||
|
entrypoint: filePath,
|
||||||
|
outDir: openapiDir,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(process.stderr.write).toBeCalledTimes(2);
|
||||||
|
expect((process.stderr.write as jest.Mock).mock.calls[0][0]).toBe(
|
||||||
|
`🪓 Document: ${blue(filePath!)} ${green('is successfully split')}
|
||||||
|
and all related files are saved to the directory: ${blue(openapiDir)} \n`
|
||||||
|
);
|
||||||
|
expect((process.stderr.write as jest.Mock).mock.calls[1][0]).toContain(
|
||||||
|
`${filePath}: split processed in <test>ms`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have correct path with paths', () => {
|
||||||
|
const openapi = require("./fixtures/spec.json");
|
||||||
|
|
||||||
jest.spyOn(openapiCore, 'slash').mockImplementation(() => 'paths/test.yaml');
|
jest.spyOn(openapiCore, 'slash').mockImplementation(() => 'paths/test.yaml');
|
||||||
jest.spyOn(path, 'relative').mockImplementation(() => 'paths/test.yaml');
|
jest.spyOn(path, 'relative').mockImplementation(() => 'paths/test.yaml');
|
||||||
iteratePaths(require("./fixtures/spec.json"), pathsDir, openapiDir);
|
iteratePathItems(openapi.paths, openapiDir, path.join(openapiDir, 'paths'), componentsFiles);
|
||||||
|
|
||||||
expect(openapiCore.slash).toHaveBeenCalledWith('paths/test.yaml');
|
expect(openapiCore.slash).toHaveBeenCalledWith('paths/test.yaml');
|
||||||
expect(path.relative).toHaveBeenCalledWith('test', 'test/paths/test.yaml');
|
expect(path.relative).toHaveBeenCalledWith('test', 'test/paths/test.yaml');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have correct paths for windows', () => {
|
it('should have correct path with webhooks', () => {
|
||||||
const pathsDir = 'test\\paths';
|
const openapi = require("./fixtures/webhooks.json");
|
||||||
const openapiDir = 'test';
|
|
||||||
|
|
||||||
jest.spyOn(openapiCore, 'slash').mockImplementation(() => 'paths\\test.yaml');
|
jest.spyOn(openapiCore, 'slash').mockImplementation(() => 'webhooks/test.yaml');
|
||||||
jest.spyOn(path, 'relative').mockImplementation(() => 'paths\\test.yaml');
|
jest.spyOn(path, 'relative').mockImplementation(() => 'webhooks/test.yaml');
|
||||||
iteratePaths(require("./fixtures/spec.json"), pathsDir, openapiDir);
|
iteratePathItems(openapi.webhooks, openapiDir, path.join(openapiDir, 'webhooks'), componentsFiles, 'webhook_');
|
||||||
expect(openapiCore.slash).toHaveBeenCalledWith('paths\\test.yaml');
|
|
||||||
expect(path.relative).toHaveBeenCalledWith('test', 'test\\paths/test.yaml');
|
expect(openapiCore.slash).toHaveBeenCalledWith('webhooks/test.yaml');
|
||||||
|
expect(path.relative).toHaveBeenCalledWith('test', 'test/webhooks/test.yaml');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have correct path with x-webhooks', () => {
|
||||||
|
const openapi = require("./fixtures/spec.json");
|
||||||
|
|
||||||
|
jest.spyOn(openapiCore, 'slash').mockImplementation(() => 'webhooks/test.yaml');
|
||||||
|
jest.spyOn(path, 'relative').mockImplementation(() => 'webhooks/test.yaml');
|
||||||
|
iteratePathItems(openapi['x-webhooks'], openapiDir, path.join(openapiDir, 'webhooks'), componentsFiles, 'webhook_');
|
||||||
|
|
||||||
|
expect(openapiCore.slash).toHaveBeenCalledWith('webhooks/test.yaml');
|
||||||
|
expect(path.relative).toHaveBeenCalledWith('test', 'test/webhooks/test.yaml');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { red, blue, yellow, green } from 'colorette';
|
import { red, blue, yellow, green } from 'colorette';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { parseYaml, slash } from '@redocly/openapi-core';
|
import { parseYaml, slash, isRef } from '@redocly/openapi-core';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
const isEqual = require('lodash.isequal');
|
const isEqual = require('lodash.isequal');
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
Oas2Definition,
|
Oas2Definition,
|
||||||
Oas3Schema,
|
Oas3Schema,
|
||||||
Oas3Definition,
|
Oas3Definition,
|
||||||
|
Oas3_1Definition,
|
||||||
Oas3Components,
|
Oas3Components,
|
||||||
Oas3ComponentName,
|
Oas3ComponentName,
|
||||||
ComponentsFiles,
|
ComponentsFiles,
|
||||||
@@ -20,19 +21,19 @@ import {
|
|||||||
OPENAPI3_COMPONENT,
|
OPENAPI3_COMPONENT,
|
||||||
COMPONENTS,
|
COMPONENTS,
|
||||||
componentsPath,
|
componentsPath,
|
||||||
PATHS,
|
|
||||||
OPENAPI3_METHOD_NAMES,
|
OPENAPI3_METHOD_NAMES,
|
||||||
OPENAPI3_COMPONENT_NAMES
|
OPENAPI3_COMPONENT_NAMES,
|
||||||
} from './types'
|
Referenced
|
||||||
|
} from './types';
|
||||||
|
|
||||||
export async function handleSplit (argv: {
|
export async function handleSplit (argv: {
|
||||||
entrypoint?: string;
|
entrypoint: string;
|
||||||
outDir: string
|
outDir: string
|
||||||
}) {
|
}) {
|
||||||
const startedAt = performance.now();
|
const startedAt = performance.now();
|
||||||
const { entrypoint, outDir } = argv;
|
const { entrypoint, outDir } = argv;
|
||||||
validateDefinitionFileName(entrypoint!);
|
validateDefinitionFileName(entrypoint!);
|
||||||
const openapi = readYaml(entrypoint!) as Oas3Definition;
|
const openapi = readYaml(entrypoint!) as Oas3Definition | Oas3_1Definition;
|
||||||
splitDefinition(openapi, outDir);
|
splitDefinition(openapi, outDir);
|
||||||
process.stderr.write(
|
process.stderr.write(
|
||||||
`🪓 Document: ${blue(entrypoint!)} ${green('is successfully split')}
|
`🪓 Document: ${blue(entrypoint!)} ${green('is successfully split')}
|
||||||
@@ -41,23 +42,16 @@ export async function handleSplit (argv: {
|
|||||||
printExecutionTime('split', startedAt, entrypoint!);
|
printExecutionTime('split', startedAt, entrypoint!);
|
||||||
}
|
}
|
||||||
|
|
||||||
function splitDefinition(openapi: Oas3Definition, openapiDir: string) {
|
function splitDefinition(openapi: Oas3Definition | Oas3_1Definition, openapiDir: string) {
|
||||||
fs.mkdirSync(openapiDir, { recursive: true });
|
fs.mkdirSync(openapiDir, { recursive: true });
|
||||||
const pathsDir = path.join(openapiDir, PATHS);
|
|
||||||
fs.mkdirSync(pathsDir, { recursive: true });
|
|
||||||
|
|
||||||
const componentsFiles: ComponentsFiles = {};
|
const componentsFiles: ComponentsFiles = {};
|
||||||
iteratePaths(openapi, pathsDir, openapiDir);
|
|
||||||
iterateComponents(openapi, openapiDir, componentsFiles);
|
iterateComponents(openapi, openapiDir, componentsFiles);
|
||||||
|
iteratePathItems(openapi.paths, openapiDir, path.join(openapiDir, 'paths'), componentsFiles);
|
||||||
|
const webhooks = (openapi as Oas3_1Definition).webhooks || (openapi as Oas3Definition)['x-webhooks'];
|
||||||
|
// use webhook_ prefix for code samples to prevent potential name-clashes with paths samples
|
||||||
|
iteratePathItems(webhooks, openapiDir, path.join(openapiDir, 'webhooks'), componentsFiles, 'webhook_');
|
||||||
|
|
||||||
function traverseDirectoryDeepCallback(filename: string, directory: string) {
|
|
||||||
if (isNotYaml(filename)) return;
|
|
||||||
const pathData = readYaml(filename);
|
|
||||||
replace$Refs(pathData, directory, componentsFiles);
|
|
||||||
writeYaml(pathData, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
traverseDirectoryDeep(pathsDir, traverseDirectoryDeepCallback);
|
|
||||||
replace$Refs(openapi, openapiDir, componentsFiles);
|
replace$Refs(openapi, openapiDir, componentsFiles);
|
||||||
writeYaml(openapi, path.join(openapiDir, 'openapi.yaml'));
|
writeYaml(openapi, path.join(openapiDir, 'openapi.yaml'));
|
||||||
}
|
}
|
||||||
@@ -82,7 +76,7 @@ function validateDefinitionFileName(fileName: string) {
|
|||||||
if (!fs.existsSync(fileName)) exitWithError(`File ${blue(fileName)} does not exist \n`);
|
if (!fs.existsSync(fileName)) exitWithError(`File ${blue(fileName)} does not exist \n`);
|
||||||
const file = loadFile(fileName);
|
const file = loadFile(fileName);
|
||||||
if ((file as Oas2Definition).swagger) exitWithError('OpenAPI 2 is not supported by this command');
|
if ((file as Oas2Definition).swagger) exitWithError('OpenAPI 2 is not supported by this command');
|
||||||
if (!(file as Oas3Definition).openapi) exitWithError('File does not conform to the OpenAPI Specification. OpenAPI version is not specified');
|
if (!(file as Oas3Definition | Oas3_1Definition).openapi) exitWithError('File does not conform to the OpenAPI Specification. OpenAPI version is not specified');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,19 +94,30 @@ function langToExt(lang: string) {
|
|||||||
return langObj[lang];
|
return langObj[lang];
|
||||||
}
|
}
|
||||||
|
|
||||||
function traverseDirectoryDeep(directory: string, callback: any) {
|
function traverseDirectoryDeep(directory: string, callback: any, componentsFiles: object) {
|
||||||
if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) return;
|
if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) return;
|
||||||
const files = fs.readdirSync(directory);
|
const files = fs.readdirSync(directory);
|
||||||
for (const f of files) {
|
for (const f of files) {
|
||||||
const filename = path.join(directory, f);
|
const filename = path.join(directory, f);
|
||||||
if (fs.statSync(filename).isDirectory()) {
|
if (fs.statSync(filename).isDirectory()) {
|
||||||
traverseDirectoryDeep(filename, callback);
|
traverseDirectoryDeep(filename, callback, componentsFiles);
|
||||||
} else {
|
} else {
|
||||||
callback(filename, directory);
|
callback(filename, directory, componentsFiles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function traverseDirectoryDeepCallback(
|
||||||
|
filename: string,
|
||||||
|
directory: string,
|
||||||
|
componentsFiles: object,
|
||||||
|
) {
|
||||||
|
if (isNotYaml(filename)) return;
|
||||||
|
const pathData = readYaml(filename);
|
||||||
|
replace$Refs(pathData, directory, componentsFiles);
|
||||||
|
writeYaml(pathData, filename);
|
||||||
|
}
|
||||||
|
|
||||||
function crawl(object: any, visitor: any) {
|
function crawl(object: any, visitor: any) {
|
||||||
if (!isObject(object)) return;
|
if (!isObject(object)) return;
|
||||||
for (const key of Object.keys(object)) {
|
for (const key of Object.keys(object)) {
|
||||||
@@ -199,7 +204,7 @@ function doesFileDiffer(filename: string, componentData: any) {
|
|||||||
return fs.existsSync(filename) && !isEqual(readYaml(filename), componentData);
|
return fs.existsSync(filename) && !isEqual(readYaml(filename), componentData);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeEmptyComponents(openapi: Oas3Definition, componentType: Oas3ComponentName) {
|
function removeEmptyComponents(openapi: Oas3Definition | Oas3_1Definition, componentType: Oas3ComponentName) {
|
||||||
if (openapi.components && isEmptyObject(openapi.components[componentType])) {
|
if (openapi.components && isEmptyObject(openapi.components[componentType])) {
|
||||||
delete openapi.components[componentType];
|
delete openapi.components[componentType];
|
||||||
}
|
}
|
||||||
@@ -237,49 +242,57 @@ function gatherComponentsFiles(
|
|||||||
componentsFiles[componentType][componentName] = { inherits, filename };
|
componentsFiles[componentType][componentName] = { inherits, filename };
|
||||||
}
|
}
|
||||||
|
|
||||||
function iteratePaths(
|
function iteratePathItems(
|
||||||
openapi: Oas3Definition,
|
pathItems: Record<string, Referenced<Oas3PathItem>> | undefined,
|
||||||
pathsDir: string,
|
openapiDir: string,
|
||||||
openapiDir: string
|
outDir: string,
|
||||||
|
componentsFiles: object,
|
||||||
|
codeSamplesPathPrefix: string = '',
|
||||||
) {
|
) {
|
||||||
const { paths } = openapi;
|
if (!pathItems) return;
|
||||||
if (paths) {
|
fs.mkdirSync(outDir, { recursive: true });
|
||||||
for (const oasPath of Object.keys(paths)) {
|
|
||||||
const pathFile = path.join(pathsDir, pathToFilename(oasPath)) + '.yaml';
|
|
||||||
const pathData: Oas3PathItem = paths[oasPath] as Oas3PathItem;
|
|
||||||
|
|
||||||
for (const method of OPENAPI3_METHOD_NAMES) {
|
for (const pathName of Object.keys(pathItems)) {
|
||||||
const methodData = pathData[method];
|
const pathFile = `${path.join(outDir, pathToFilename(pathName))}.yaml`;
|
||||||
const methodDataXCode = methodData?.['x-code-samples'] || methodData?.['x-codeSamples'];
|
const pathData = pathItems[pathName] as Oas3PathItem;
|
||||||
if (!methodDataXCode || !Array.isArray(methodDataXCode)) { continue; }
|
|
||||||
for (const sample of methodDataXCode) {
|
|
||||||
if (sample.source && (sample.source as any).$ref) continue;
|
|
||||||
const sampleFileName = path.join(
|
|
||||||
openapiDir,
|
|
||||||
'code_samples',
|
|
||||||
sample.lang,
|
|
||||||
pathToFilename(oasPath),
|
|
||||||
method + langToExt(sample.lang)
|
|
||||||
);
|
|
||||||
|
|
||||||
fs.mkdirSync(path.dirname(sampleFileName), { recursive: true });
|
if (isRef(pathData)) continue;
|
||||||
fs.writeFileSync(sampleFileName, sample.source);
|
|
||||||
// @ts-ignore
|
for (const method of OPENAPI3_METHOD_NAMES) {
|
||||||
sample.source = {
|
const methodData = pathData[method];
|
||||||
$ref: slash(path.relative(pathsDir, sampleFileName))
|
const methodDataXCode = methodData?.['x-code-samples'] || methodData?.['x-codeSamples'];
|
||||||
};
|
if (!methodDataXCode || !Array.isArray(methodDataXCode)) {
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
for (const sample of methodDataXCode) {
|
||||||
|
if (sample.source && (sample.source as any).$ref) continue;
|
||||||
|
const sampleFileName = path.join(
|
||||||
|
openapiDir,
|
||||||
|
'code_samples',
|
||||||
|
sample.lang,
|
||||||
|
codeSamplesPathPrefix + pathToFilename(pathName),
|
||||||
|
method + langToExt(sample.lang),
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.mkdirSync(path.dirname(sampleFileName), { recursive: true });
|
||||||
|
fs.writeFileSync(sampleFileName, sample.source);
|
||||||
|
// @ts-ignore
|
||||||
|
sample.source = {
|
||||||
|
$ref: slash(path.relative(outDir, sampleFileName))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
writeYaml(pathData, pathFile);
|
|
||||||
paths[oasPath] = {
|
|
||||||
$ref: slash(path.relative(openapiDir, pathFile))
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
writeYaml(pathData, pathFile);
|
||||||
|
pathItems[pathName] = {
|
||||||
|
$ref: slash(path.relative(openapiDir, pathFile))
|
||||||
|
};
|
||||||
|
|
||||||
|
traverseDirectoryDeep(outDir, traverseDirectoryDeepCallback, componentsFiles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function iterateComponents(
|
function iterateComponents(
|
||||||
openapi: Oas3Definition,
|
openapi: Oas3Definition | Oas3_1Definition,
|
||||||
openapiDir: string,
|
openapiDir: string,
|
||||||
componentsFiles: ComponentsFiles
|
componentsFiles: ComponentsFiles
|
||||||
) {
|
) {
|
||||||
@@ -331,6 +344,4 @@ function iterateComponents(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export { iteratePathItems };
|
||||||
iteratePaths,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import {
|
import {
|
||||||
Oas3Schema,
|
Oas3Schema,
|
||||||
|
Oas3_1Schema,
|
||||||
Oas3Definition,
|
Oas3Definition,
|
||||||
|
Oas3_1Definition,
|
||||||
Oas3Components,
|
Oas3Components,
|
||||||
Oas3PathItem,
|
Oas3PathItem,
|
||||||
Oas3Paths,
|
Oas3Paths,
|
||||||
Oas3ComponentName,
|
Oas3ComponentName,
|
||||||
Oas2Definition
|
Oas3_1Webhooks,
|
||||||
|
Oas2Definition,
|
||||||
|
Referenced
|
||||||
} from "@redocly/openapi-core";
|
} from "@redocly/openapi-core";
|
||||||
export { Oas3Definition, Oas2Definition, Oas3Components, Oas3Paths, Oas3PathItem, Oas3ComponentName, Oas3Schema }
|
export { Oas3_1Definition, Oas3Definition, Oas2Definition, Oas3Components, Oas3Paths, Oas3PathItem, Oas3ComponentName, Oas3_1Schema, Oas3Schema, Oas3_1Webhooks, Referenced }
|
||||||
export type Definition = Oas3Definition | Oas2Definition;
|
export type Definition = Oas3_1Definition | Oas3Definition | Oas2Definition;
|
||||||
export interface ComponentsFiles {
|
export interface ComponentsFiles {
|
||||||
[schemas: string]: any;
|
[schemas: string]: any;
|
||||||
}
|
}
|
||||||
@@ -18,6 +22,8 @@ export interface refObj {
|
|||||||
|
|
||||||
export const COMPONENTS = 'components';
|
export const COMPONENTS = 'components';
|
||||||
export const PATHS = 'paths';
|
export const PATHS = 'paths';
|
||||||
|
export const WEBHOOKS = 'webhooks';
|
||||||
|
export const xWEBHOOKS = 'x-webhooks';
|
||||||
export const componentsPath = `#/${COMPONENTS}/`;
|
export const componentsPath = `#/${COMPONENTS}/`;
|
||||||
|
|
||||||
enum OPENAPI3_METHOD {
|
enum OPENAPI3_METHOD {
|
||||||
|
|||||||
@@ -35,13 +35,21 @@ yargs
|
|||||||
'split [entrypoint]',
|
'split [entrypoint]',
|
||||||
'Split definition into a multi-file structure.',
|
'Split definition into a multi-file structure.',
|
||||||
(yargs) =>
|
(yargs) =>
|
||||||
yargs.positional('entrypoint', { type: 'string' }).option({
|
yargs
|
||||||
outDir: {
|
.positional('entrypoint', {
|
||||||
description: 'Output directory where files will be saved.',
|
description: 'API definition file that you want to split',
|
||||||
required: true,
|
type: 'string'
|
||||||
type: 'string',
|
})
|
||||||
},
|
.option({
|
||||||
}),
|
outDir: {
|
||||||
|
description: 'Output directory where files will be saved.',
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.demandOption(
|
||||||
|
'entrypoint'
|
||||||
|
),
|
||||||
handleSplit,
|
handleSplit,
|
||||||
)
|
)
|
||||||
.command(
|
.command(
|
||||||
@@ -207,7 +215,7 @@ yargs
|
|||||||
description: 'Remove unused components.',
|
description: 'Remove unused components.',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
(argv) => {
|
(argv) => {
|
||||||
handleBundle(argv, version);
|
handleBundle(argv, version);
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function pathToFilename(path: string) {
|
|||||||
return path
|
return path
|
||||||
.replace(/~1/g, '/')
|
.replace(/~1/g, '/')
|
||||||
.replace(/~0/g, '~')
|
.replace(/~0/g, '~')
|
||||||
.substring(1)
|
.replace(/^\//, '')
|
||||||
.replace(/\//g, '@');
|
.replace(/\//g, '@');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,16 @@ export { Oas2Types } from './types/oas2';
|
|||||||
export { ConfigTypes } from './types/redocly-yaml';
|
export { ConfigTypes } from './types/redocly-yaml';
|
||||||
export {
|
export {
|
||||||
Oas3Definition,
|
Oas3Definition,
|
||||||
|
Oas3_1Definition,
|
||||||
Oas3Components,
|
Oas3Components,
|
||||||
Oas3PathItem,
|
Oas3PathItem,
|
||||||
Oas3Paths,
|
Oas3Paths,
|
||||||
Oas3ComponentName,
|
Oas3ComponentName,
|
||||||
Oas3Schema,
|
Oas3Schema,
|
||||||
|
Oas3_1Schema,
|
||||||
Oas3Tag,
|
Oas3Tag,
|
||||||
|
Oas3_1Webhooks,
|
||||||
|
Referenced
|
||||||
} from './typings/openapi';
|
} from './typings/openapi';
|
||||||
export { Oas2Definition } from './typings/swagger';
|
export { Oas2Definition } from './typings/swagger';
|
||||||
export { StatsAccumulator, StatsName } from './typings/common';
|
export { StatsAccumulator, StatsName } from './typings/common';
|
||||||
@@ -30,7 +34,7 @@ export {
|
|||||||
makeDocumentFromString,
|
makeDocumentFromString,
|
||||||
} from './resolve';
|
} from './resolve';
|
||||||
export { parseYaml, stringifyYaml } from './js-yaml';
|
export { parseYaml, stringifyYaml } from './js-yaml';
|
||||||
export { unescapePointer } from './ref-utils';
|
export { unescapePointer, isRef } from './ref-utils';
|
||||||
export { detectOpenAPI, OasMajorVersion, openAPIMajor, OasVersion } from './oas-types';
|
export { detectOpenAPI, OasMajorVersion, openAPIMajor, OasVersion } from './oas-types';
|
||||||
export { normalizeVisitors } from './visitors';
|
export { normalizeVisitors } from './visitors';
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface Oas3Definition {
|
|||||||
security?: Oas3SecurityRequirement[];
|
security?: Oas3SecurityRequirement[];
|
||||||
tags?: Oas3Tag[];
|
tags?: Oas3Tag[];
|
||||||
externalDocs?: Oas3ExternalDocs;
|
externalDocs?: Oas3ExternalDocs;
|
||||||
|
'x-webhooks'?: Oas3_1Webhooks;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Oas3Info {
|
export interface Oas3Info {
|
||||||
@@ -154,6 +155,14 @@ export interface Oas3_1Schema extends Oas3Schema {
|
|||||||
examples?: any[];
|
examples?: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Oas3_1Definition extends Oas3Definition {
|
||||||
|
webhooks?: Oas3_1Webhooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Oas3_1Webhooks {
|
||||||
|
[webhook: string]: Referenced<Oas3PathItem>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Oas3Discriminator {
|
export interface Oas3Discriminator {
|
||||||
propertyName: string;
|
propertyName: string;
|
||||||
mapping?: { [name: string]: string };
|
mapping?: { [name: string]: string };
|
||||||
|
|||||||
Reference in New Issue
Block a user