mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
fix: openapi spec should only include base path once (#1755)
* fix: openapi spec should only include base path once
This commit is contained in:
parent
f2257eb45b
commit
54d28471f7
7576
coverage/clover.xml
7576
coverage/clover.xml
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
11513
coverage/lcov.info
11513
coverage/lcov.info
File diff suppressed because it is too large
Load Diff
67142
coverage/report.json
67142
coverage/report.json
File diff suppressed because one or more lines are too long
@ -39,10 +39,10 @@ tags:
|
|||||||
info:
|
info:
|
||||||
title: Unleash API
|
title: Unleash API
|
||||||
description: |-
|
description: |-
|
||||||
|
|
||||||
> The Open API specifications are currently considered a **"beta feature"** and will not cover the full Unleash Admin API.
|
> The Open API specifications are currently considered a **"beta feature"** and will not cover the full Unleash Admin API.
|
||||||
> You can follow the progress on making OAS official in [GitHub issue 1391](https://github.com/Unleash/unleash/issues/1391)
|
> You can follow the progress on making OAS official in [GitHub issue 1391](https://github.com/Unleash/unleash/issues/1391)
|
||||||
|
|
||||||
Unleash is an open source feature flag and toggle system for all your applications and services.
|
Unleash is an open source feature flag and toggle system for all your applications and services.
|
||||||
|
|
||||||
# Try it out
|
# Try it out
|
||||||
@ -133,9 +133,9 @@ paths:
|
|||||||
get:
|
get:
|
||||||
summary: Fetches all feature toggles from the Unleash server.
|
summary: Fetches all feature toggles from the Unleash server.
|
||||||
description: |-
|
description: |-
|
||||||
The response returns all active feature toggles and their current strategy configuration:
|
The response returns all active feature toggles and their current strategy configuration:
|
||||||
- Feature toggles will have *at least* one strategy
|
- Feature toggles will have *at least* one strategy
|
||||||
- Strategies have a `name` and `parameters` map.
|
- Strategies have a `name` and `parameters` map.
|
||||||
operationId: getClientFeatures
|
operationId: getClientFeatures
|
||||||
externalDocs:
|
externalDocs:
|
||||||
description: Activation strategies
|
description: Activation strategies
|
||||||
@ -598,7 +598,7 @@ paths:
|
|||||||
'200':
|
'200':
|
||||||
$ref: '#/components/responses/featureAccessResponse'
|
$ref: '#/components/responses/featureAccessResponse'
|
||||||
'401':
|
'401':
|
||||||
$ref: '#/components/responses/notAuthorizedResponse'
|
$ref: '#/components/responses/notAuthorizedResponse'
|
||||||
x-code-samples:
|
x-code-samples:
|
||||||
- lang: 'cURL'
|
- lang: 'cURL'
|
||||||
source: |
|
source: |
|
||||||
@ -843,73 +843,73 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
successResponse:
|
successResponse:
|
||||||
description: "Successful response"
|
description: 'Successful response'
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/200'
|
$ref: '#/components/schemas/200'
|
||||||
notAuthorizedResponse:
|
notAuthorizedResponse:
|
||||||
description: "Not authorized"
|
description: 'Not authorized'
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/401'
|
$ref: '#/components/schemas/401'
|
||||||
clientResponse:
|
clientResponse:
|
||||||
description: "Successful response"
|
description: 'Successful response'
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/200Client'
|
$ref: '#/components/schemas/200Client'
|
||||||
strategyExistsResponse:
|
strategyExistsResponse:
|
||||||
description: "Strategy already exists"
|
description: 'Strategy already exists'
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/409'
|
$ref: '#/components/schemas/409'
|
||||||
strategyResponse:
|
strategyResponse:
|
||||||
description: "Successful response"
|
description: 'Successful response'
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/200strategy'
|
$ref: '#/components/schemas/200strategy'
|
||||||
seenTogglesResponse:
|
seenTogglesResponse:
|
||||||
description: "Successful response"
|
description: 'Successful response'
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/200seen'
|
$ref: '#/components/schemas/200seen'
|
||||||
featureAccessResponse:
|
featureAccessResponse:
|
||||||
description: "Successful response"
|
description: 'Successful response'
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/200feature'
|
$ref: '#/components/schemas/200feature'
|
||||||
appDetailsResponse:
|
appDetailsResponse:
|
||||||
description: "Successful response"
|
description: 'Successful response'
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/200appdetails'
|
$ref: '#/components/schemas/200appdetails'
|
||||||
applicationsResponse:
|
applicationsResponse:
|
||||||
description: "Successful response"
|
description: 'Successful response'
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/applicationArray'
|
$ref: '#/components/schemas/applicationArray'
|
||||||
eventsResponse:
|
eventsResponse:
|
||||||
description: "Successful response"
|
description: 'Successful response'
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/200-events'
|
$ref: '#/components/schemas/200-events'
|
||||||
exportResponse:
|
exportResponse:
|
||||||
description: "Successful response"
|
description: 'Successful response'
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/200export'
|
$ref: '#/components/schemas/200export'
|
||||||
featureTypeResponse:
|
featureTypeResponse:
|
||||||
description: "Successful response"
|
description: 'Successful response'
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
@ -1205,7 +1205,7 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
example: '2016-12-09T14:56:36.730Z'
|
example: '2016-12-09T14:56:36.730Z'
|
||||||
updatedAt:
|
updatedAt:
|
||||||
description: 'The last time the application ''talked'' to Unleash (sent metrics, registered, fetched feature toggles) - in [ISO8601 format](https://www.w3.org/TR/NOTE-datetime)'
|
description: "The last time the application 'talked' to Unleash (sent metrics, registered, fetched feature toggles) - in [ISO8601 format](https://www.w3.org/TR/NOTE-datetime)"
|
||||||
type: string
|
type: string
|
||||||
example: '2020-11-14T09:55:23.653Z'
|
example: '2020-11-14T09:55:23.653Z'
|
||||||
description:
|
description:
|
||||||
@ -1222,7 +1222,7 @@ components:
|
|||||||
description: Deprecated. Do not use
|
description: Deprecated. Do not use
|
||||||
type: string
|
type: string
|
||||||
icon:
|
icon:
|
||||||
description: 'The application''s icon. Must be one of the [Material Design icon names](https://material.io/resources/icons/?style=baseline)'
|
description: "The application's icon. Must be one of the [Material Design icon names](https://material.io/resources/icons/?style=baseline)"
|
||||||
type: string
|
type: string
|
||||||
example: comment_bank
|
example: comment_bank
|
||||||
x-tags:
|
x-tags:
|
||||||
@ -1270,7 +1270,7 @@ components:
|
|||||||
minLength: 1
|
minLength: 1
|
||||||
example: '::ffff:127.0.0.1'
|
example: '::ffff:127.0.0.1'
|
||||||
lastSeen:
|
lastSeen:
|
||||||
description: 'The last time the application ''talked'' to Unleash (sent metrics, registered, fetched feature toggles) - in [ISO8601 format](https://www.w3.org/TR/NOTE-datetime)'
|
description: "The last time the application 'talked' to Unleash (sent metrics, registered, fetched feature toggles) - in [ISO8601 format](https://www.w3.org/TR/NOTE-datetime)"
|
||||||
type: string
|
type: string
|
||||||
minLength: 1
|
minLength: 1
|
||||||
example: '2020-11-14T11:17:24.482Z'
|
example: '2020-11-14T11:17:24.482Z'
|
||||||
|
@ -39,7 +39,6 @@ export default async function getApp(
|
|||||||
app.disable('x-powered-by');
|
app.disable('x-powered-by');
|
||||||
app.set('port', config.server.port);
|
app.set('port', config.server.port);
|
||||||
app.locals.baseUriPath = baseUriPath;
|
app.locals.baseUriPath = baseUriPath;
|
||||||
|
|
||||||
if (config.server.serverMetrics && config.eventBus) {
|
if (config.server.serverMetrics && config.eventBus) {
|
||||||
app.use(responseTimeMetrics(config.eventBus));
|
app.use(responseTimeMetrics(config.eventBus));
|
||||||
}
|
}
|
||||||
|
@ -72,8 +72,46 @@ test('removeJsonSchemaProps', () => {
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('createOpenApiSchema url', () => {
|
describe('createOpenApiSchema', () => {
|
||||||
expect(createOpenApiSchema('https://example.com').servers[0].url).toEqual(
|
test('createOpenApiSchema url', () => {
|
||||||
'https://example.com',
|
expect(
|
||||||
);
|
createOpenApiSchema({
|
||||||
|
unleashUrl: 'https://example.com',
|
||||||
|
baseUriPath: '',
|
||||||
|
}).servers[0].url,
|
||||||
|
).toEqual('https://example.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('if baseurl is set strips from serverUrl', () => {
|
||||||
|
expect(
|
||||||
|
createOpenApiSchema({
|
||||||
|
unleashUrl: 'https://example.com/demo2',
|
||||||
|
baseUriPath: '/demo2',
|
||||||
|
}).servers[0].url,
|
||||||
|
).toEqual('https://example.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('if baseurl does not end with suffix, cowardly refuses to strip', () => {
|
||||||
|
expect(
|
||||||
|
createOpenApiSchema({
|
||||||
|
unleashUrl: 'https://example.com/demo2',
|
||||||
|
baseUriPath: 'example',
|
||||||
|
}).servers[0].url,
|
||||||
|
).toEqual('https://example.com/demo2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('avoids double trailing slash', () => {
|
||||||
|
expect(
|
||||||
|
createOpenApiSchema({
|
||||||
|
unleashUrl: 'https://example.com/example/',
|
||||||
|
baseUriPath: 'example',
|
||||||
|
}).servers[0].url,
|
||||||
|
).toEqual('https://example.com');
|
||||||
|
expect(
|
||||||
|
createOpenApiSchema({
|
||||||
|
unleashUrl: 'https://example.com/example/',
|
||||||
|
baseUriPath: '/example',
|
||||||
|
}).servers[0].url,
|
||||||
|
).toEqual('https://example.com');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -83,6 +83,8 @@ import { strategySchema } from './spec/strategy-schema';
|
|||||||
import { strategiesSchema } from './spec/strategies-schema';
|
import { strategiesSchema } from './spec/strategies-schema';
|
||||||
import { upsertStrategySchema } from './spec/upsert-strategy-schema';
|
import { upsertStrategySchema } from './spec/upsert-strategy-schema';
|
||||||
import { clientApplicationSchema } from './spec/client-application-schema';
|
import { clientApplicationSchema } from './spec/client-application-schema';
|
||||||
|
import { IServerOption } from '../types';
|
||||||
|
import { URL } from 'url';
|
||||||
|
|
||||||
// All schemas in `openapi/spec` should be listed here.
|
// All schemas in `openapi/spec` should be listed here.
|
||||||
export const schemas = {
|
export const schemas = {
|
||||||
@ -226,12 +228,31 @@ export const removeJsonSchemaProps = <T extends JsonSchemaProps>(
|
|||||||
return omitKeys(schema, '$id', 'components');
|
return omitKeys(schema, '$id', 'components');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createOpenApiSchema = (
|
const findRootUrl: (unleashUrl: string, baseUriPath: string) => string = (
|
||||||
serverUrl?: string,
|
unleashUrl: string,
|
||||||
): Omit<OpenAPIV3.Document, 'paths'> => {
|
baseUriPath?: string,
|
||||||
|
) => {
|
||||||
|
if (!baseUriPath) {
|
||||||
|
return unleashUrl;
|
||||||
|
}
|
||||||
|
const baseUrl = new URL(unleashUrl);
|
||||||
|
if (baseUrl.pathname.indexOf(baseUriPath) >= 0) {
|
||||||
|
return `${baseUrl.protocol}//${baseUrl.host}`;
|
||||||
|
}
|
||||||
|
return baseUrl.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createOpenApiSchema = ({
|
||||||
|
unleashUrl,
|
||||||
|
baseUriPath,
|
||||||
|
}: Pick<IServerOption, 'unleashUrl' | 'baseUriPath'>): Omit<
|
||||||
|
OpenAPIV3.Document,
|
||||||
|
'paths'
|
||||||
|
> => {
|
||||||
|
const url = findRootUrl(unleashUrl, baseUriPath);
|
||||||
return {
|
return {
|
||||||
openapi: '3.0.3',
|
openapi: '3.0.3',
|
||||||
servers: serverUrl ? [{ url: serverUrl }] : [],
|
servers: url ? [{ url }] : [],
|
||||||
info: {
|
info: {
|
||||||
title: 'Unleash API',
|
title: 'Unleash API',
|
||||||
version: process.env.npm_package_version!,
|
version: process.env.npm_package_version!,
|
||||||
|
@ -24,7 +24,7 @@ export class OpenApiService {
|
|||||||
|
|
||||||
this.api = openapi(
|
this.api = openapi(
|
||||||
this.docsPath(),
|
this.docsPath(),
|
||||||
createOpenApiSchema(config.server?.unleashUrl),
|
createOpenApiSchema(config.server),
|
||||||
{ coerce: true },
|
{ coerce: true },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user