mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-10 01:16:39 +02:00
chore: Improve openapi documentation for tags (#3496)
## About the changes 1. Create tag should not throw a 500 when bad data is provided 2. Added summary, description and examples to open API endpoints --------- Co-authored-by: Nuno Góis <github@nunogois.com>
This commit is contained in:
parent
c1a1a0fdeb
commit
37beaa611f
@ -1,5 +1,7 @@
|
||||
import { FromSchema } from 'json-schema-to-ts';
|
||||
|
||||
export const TAG_MIN_LENGTH = 2;
|
||||
export const TAG_MAX_LENGTH = 50;
|
||||
export const tagSchema = {
|
||||
$id: '#/components/schemas/tagSchema',
|
||||
type: 'object',
|
||||
@ -8,11 +10,20 @@ export const tagSchema = {
|
||||
properties: {
|
||||
value: {
|
||||
type: 'string',
|
||||
minLength: TAG_MIN_LENGTH,
|
||||
maxLength: TAG_MAX_LENGTH,
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
minLength: TAG_MIN_LENGTH,
|
||||
maxLength: TAG_MAX_LENGTH,
|
||||
default: 'simple',
|
||||
},
|
||||
},
|
||||
example: {
|
||||
value: 'tag-value',
|
||||
type: 'simple',
|
||||
},
|
||||
components: {},
|
||||
} as const;
|
||||
|
||||
|
@ -20,6 +20,20 @@ export const updateTagsSchema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
example: {
|
||||
addedTags: [
|
||||
{
|
||||
value: 'tag-to-add',
|
||||
type: 'simple',
|
||||
},
|
||||
],
|
||||
removedTags: [
|
||||
{
|
||||
value: 'tag-to-remove',
|
||||
type: 'simple',
|
||||
},
|
||||
],
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
tagSchema,
|
||||
|
@ -29,7 +29,10 @@ import {
|
||||
createResponseSchema,
|
||||
resourceCreatedResponseSchema,
|
||||
} from '../../openapi/util/create-response-schema';
|
||||
import { emptyResponse } from '../../openapi/util/standard-responses';
|
||||
import {
|
||||
emptyResponse,
|
||||
getStandardResponses,
|
||||
} from '../../openapi/util/standard-responses';
|
||||
import { UpdateTagsSchema } from '../../openapi/spec/update-tags-schema';
|
||||
|
||||
const version = 1;
|
||||
@ -110,9 +113,15 @@ class FeatureController extends Controller {
|
||||
permission: NONE,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
summary: 'Get all tags for a feature.',
|
||||
description:
|
||||
'Retrieves all the tags for a feature name. If the feature does not exist it returns an empty list.',
|
||||
tags: ['Features'],
|
||||
operationId: 'listTags',
|
||||
responses: { 200: createResponseSchema('tagsSchema') },
|
||||
responses: {
|
||||
200: createResponseSchema('tagsSchema'),
|
||||
...getStandardResponses(401),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -124,11 +133,15 @@ class FeatureController extends Controller {
|
||||
handler: this.addTag,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
summary: 'Adds a tag to a feature.',
|
||||
description:
|
||||
'Adds a tag to a feature if the feature and tag type exist in the system. The operation is idempotent, so adding an existing tag will result in a successful response.',
|
||||
tags: ['Features'],
|
||||
operationId: 'addTag',
|
||||
requestBody: createRequestSchema('tagSchema'),
|
||||
responses: {
|
||||
201: resourceCreatedResponseSchema('tagSchema'),
|
||||
...getStandardResponses(400, 401, 403, 404),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -141,11 +154,15 @@ class FeatureController extends Controller {
|
||||
handler: this.updateTags,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
summary: 'Updates multiple tags for a feature.',
|
||||
description:
|
||||
'Receives a list of tags to add and a list of tags to remove that are mandatory but can be empty. All tags under addedTags are first added to the feature and then all tags under removedTags are removed from the feature.',
|
||||
tags: ['Features'],
|
||||
operationId: 'updateTags',
|
||||
requestBody: createRequestSchema('updateTagsSchema'),
|
||||
responses: {
|
||||
200: resourceCreatedResponseSchema('tagsSchema'),
|
||||
...getStandardResponses(400, 401, 403, 404),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -159,9 +176,15 @@ class FeatureController extends Controller {
|
||||
handler: this.removeTag,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
summary: 'Removes a tag from a feature.',
|
||||
description:
|
||||
'Removes a tag from a feature. If the feature exists but the tag does not, it returns a successful response.',
|
||||
tags: ['Features'],
|
||||
operationId: 'removeTag',
|
||||
responses: { 200: emptyResponse },
|
||||
responses: {
|
||||
200: emptyResponse,
|
||||
...getStandardResponses(404),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
import { IEventStore } from '../types/stores/event-store';
|
||||
import { ITagStore } from '../types/stores/tag-store';
|
||||
import { ITag } from '../types/model';
|
||||
import { BadDataError, FOREIGN_KEY_VIOLATION } from '../../lib/error';
|
||||
|
||||
class FeatureTagService {
|
||||
private tagStore: ITagStore;
|
||||
@ -129,12 +130,20 @@ class FeatureTagService {
|
||||
await this.tagStore.getTag(tag.type, tag.value);
|
||||
} catch (error) {
|
||||
if (error instanceof NotFoundError) {
|
||||
await this.tagStore.createTag(tag);
|
||||
await this.eventStore.store({
|
||||
type: TAG_CREATED,
|
||||
createdBy: userName,
|
||||
data: tag,
|
||||
});
|
||||
try {
|
||||
await this.tagStore.createTag(tag);
|
||||
await this.eventStore.store({
|
||||
type: TAG_CREATED,
|
||||
createdBy: userName,
|
||||
data: tag,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code === FOREIGN_KEY_VIOLATION) {
|
||||
throw new BadDataError(
|
||||
`Tag type '${tag.type}' does not exist`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,16 @@
|
||||
import Joi from 'joi';
|
||||
|
||||
import { customJoi } from '../routes/util';
|
||||
import { TAG_MAX_LENGTH, TAG_MIN_LENGTH } from '../../lib/openapi';
|
||||
|
||||
export const tagSchema = Joi.object()
|
||||
.keys({
|
||||
value: Joi.string().min(2).max(50),
|
||||
type: customJoi.isUrlFriendly().min(2).max(50).default('simple'),
|
||||
value: Joi.string().min(TAG_MIN_LENGTH).max(TAG_MAX_LENGTH),
|
||||
type: customJoi
|
||||
.isUrlFriendly()
|
||||
.min(TAG_MIN_LENGTH)
|
||||
.max(TAG_MAX_LENGTH)
|
||||
.default('simple'),
|
||||
})
|
||||
.options({
|
||||
allowUnknown: false,
|
||||
|
@ -3706,11 +3706,20 @@ Stats are divided into current and previous **windows**.
|
||||
},
|
||||
"tagSchema": {
|
||||
"additionalProperties": false,
|
||||
"example": {
|
||||
"type": "simple",
|
||||
"value": "tag-value",
|
||||
},
|
||||
"properties": {
|
||||
"type": {
|
||||
"default": "simple",
|
||||
"maxLength": 50,
|
||||
"minLength": 2,
|
||||
"type": "string",
|
||||
},
|
||||
"value": {
|
||||
"maxLength": 50,
|
||||
"minLength": 2,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
@ -4019,6 +4028,20 @@ Stats are divided into current and previous **windows**.
|
||||
},
|
||||
"updateTagsSchema": {
|
||||
"additionalProperties": false,
|
||||
"example": {
|
||||
"addedTags": [
|
||||
{
|
||||
"type": "simple",
|
||||
"value": "tag-to-add",
|
||||
},
|
||||
],
|
||||
"removedTags": [
|
||||
{
|
||||
"type": "simple",
|
||||
"value": "tag-to-remove",
|
||||
},
|
||||
],
|
||||
},
|
||||
"properties": {
|
||||
"addedTags": {
|
||||
"items": {
|
||||
@ -5474,6 +5497,7 @@ If the provided project does not exist, the list of events will be empty.",
|
||||
},
|
||||
"/api/admin/features/{featureName}/tags": {
|
||||
"get": {
|
||||
"description": "Retrieves all the tags for a feature name. If the feature does not exist it returns an empty list.",
|
||||
"operationId": "listTags",
|
||||
"parameters": [
|
||||
{
|
||||
@ -5496,12 +5520,17 @@ If the provided project does not exist, the list of events will be empty.",
|
||||
},
|
||||
"description": "tagsSchema",
|
||||
},
|
||||
"401": {
|
||||
"description": "Authorization information is missing or invalid. Provide a valid API token as the \`authorization\` header, e.g. \`authorization:*.*.my-admin-token\`.",
|
||||
},
|
||||
},
|
||||
"summary": "Get all tags for a feature.",
|
||||
"tags": [
|
||||
"Features",
|
||||
],
|
||||
},
|
||||
"post": {
|
||||
"description": "Adds a tag to a feature if the feature and tag type exist in the system. The operation is idempotent, so adding an existing tag will result in a successful response.",
|
||||
"operationId": "addTag",
|
||||
"parameters": [
|
||||
{
|
||||
@ -5544,12 +5573,26 @@ If the provided project does not exist, the list of events will be empty.",
|
||||
},
|
||||
},
|
||||
},
|
||||
"400": {
|
||||
"description": "The request data does not match what we expect.",
|
||||
},
|
||||
"401": {
|
||||
"description": "Authorization information is missing or invalid. Provide a valid API token as the \`authorization\` header, e.g. \`authorization:*.*.my-admin-token\`.",
|
||||
},
|
||||
"403": {
|
||||
"description": "User credentials are valid but does not have enough privileges to execute this operation",
|
||||
},
|
||||
"404": {
|
||||
"description": "The requested resource was not found.",
|
||||
},
|
||||
},
|
||||
"summary": "Adds a tag to a feature.",
|
||||
"tags": [
|
||||
"Features",
|
||||
],
|
||||
},
|
||||
"put": {
|
||||
"description": "Receives a list of tags to add and a list of tags to remove that are mandatory but can be empty. All tags under addedTags are first added to the feature and then all tags under removedTags are removed from the feature.",
|
||||
"operationId": "updateTags",
|
||||
"parameters": [
|
||||
{
|
||||
@ -5592,7 +5635,20 @@ If the provided project does not exist, the list of events will be empty.",
|
||||
},
|
||||
},
|
||||
},
|
||||
"400": {
|
||||
"description": "The request data does not match what we expect.",
|
||||
},
|
||||
"401": {
|
||||
"description": "Authorization information is missing or invalid. Provide a valid API token as the \`authorization\` header, e.g. \`authorization:*.*.my-admin-token\`.",
|
||||
},
|
||||
"403": {
|
||||
"description": "User credentials are valid but does not have enough privileges to execute this operation",
|
||||
},
|
||||
"404": {
|
||||
"description": "The requested resource was not found.",
|
||||
},
|
||||
},
|
||||
"summary": "Updates multiple tags for a feature.",
|
||||
"tags": [
|
||||
"Features",
|
||||
],
|
||||
@ -5600,6 +5656,7 @@ If the provided project does not exist, the list of events will be empty.",
|
||||
},
|
||||
"/api/admin/features/{featureName}/tags/{type}/{value}": {
|
||||
"delete": {
|
||||
"description": "Removes a tag from a feature. If the feature exists but the tag does not, it returns a successful response.",
|
||||
"operationId": "removeTag",
|
||||
"parameters": [
|
||||
{
|
||||
@ -5631,7 +5688,11 @@ If the provided project does not exist, the list of events will be empty.",
|
||||
"200": {
|
||||
"description": "This response has no body.",
|
||||
},
|
||||
"404": {
|
||||
"description": "The requested resource was not found.",
|
||||
},
|
||||
},
|
||||
"summary": "Removes a tag from a feature.",
|
||||
"tags": [
|
||||
"Features",
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user