Skip to content

Commit

Permalink
documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
teunmooij committed Jul 15, 2023
1 parent e88d254 commit aba3b90
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 18 deletions.
4 changes: 4 additions & 0 deletions packages/openapi/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.3.0

- Added detailed documentation of custom endpoints, including summary, description, success and alternative response schemas and query parameters.

## 1.2.1

- Added `unsupported Payload version` schema to be generated if user attempt to create a schema with a Payload version that has known schema generation issues
Expand Down
76 changes: 76 additions & 0 deletions packages/openapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,82 @@ const Media: CollectionConfig = {
};
```

## Documentation of custom endpoints

Custom endpoints on all levels can be fully documented, either by using the `defineEndpoint` helper:

```ts
import { CollectionConfig } from 'payload/types';
import { defineEndpoint } from 'payload-openapi';

const Post: CollectionConfig = {
slug: 'posts',
endpoints: [
defineEndpoint({
summary: 'media by category',
description: 'gets the media of the given category',
path: '/category/:category',
method: 'get',
responseSchema: 'posts',
errorResponseSchemas: {
404: 'error',
},
queryParameters: {
limit: { schema: { type: 'number' } },
page: { schema: { type: 'number' } },
sort: { scema: { type: 'string' } },
},
handler: (req, res) => {
// ... handler implementation
},
}),
],
// ... Rest of collection config
};
```

or by setting the `custom.openapi` property:

```ts
import { CollectionConfig } from 'payload/types';
import type { EndpointDocumentation } from 'payload-openapi';

const documentation: EndpointDocumentation = {
summary: 'media by category',
description: 'gets the media of the given category',
responseSchema: 'posts',
errorResponseSchemas: {
404: { type: 'object', properties: { message: { type: 'string' } }, required: ['message'] },
},
queryParameters: {
limit: { schema: { type: 'number' } },
page: { schema: { type: 'number' } },
sort: { scema: { type: 'string' } },
},
};

const Post: CollectionConfig = {
slug: 'posts',
endpoints: [
{
path: '/category/:category',
method: 'get',
handler: (req, res) => {
// ... handler implementation
},
custom: {
openapi: documentation,
},
},
],
// ... Rest of collection config
};
```

Response schemas can be defined as openapi schema objects or as string, referencing any of the schemas defined in the schema section of the openapi document.

To exclude a custom endpoint from the documentation, set the `custom.openapi` property to `false`.

## Excluding unused endpoints

In Payload `collections` and `globals` have a standard set of available endpoints. In some situations you might not want to use some of these endpoints. In those situations you probably have used an access method that looks something like this: `() => false;`. This blocks all traffic, but the endpoint is still part of the openapi documentation.
Expand Down
18 changes: 10 additions & 8 deletions packages/openapi/src/config-extensions/custom-endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ export interface EndpointDocumentation {
description: string;
responseSchema: OpenAPIV3.SchemaObject | string;
errorResponseSchemas?: Record<number, OpenAPIV3.SchemaObject | string>;
queryParamters?: {
name: string;
description?: string;
required?: boolean;
schema: OpenAPIV3.SchemaObject | string;
}[];
queryParameters?: Record<
string,
{
description?: string;
required?: boolean;
schema: OpenAPIV3.SchemaObject | string;
}
>;
}

type DocumentedEndpoint = Endpoint & EndpointDocumentation;

export function defineEndpoint(endpoint: DocumentedEndpoint): Endpoint {
const { summary, description, responseSchema, errorResponseSchemas, queryParamters, custom, ...rest } = endpoint;
const { summary, description, responseSchema, errorResponseSchemas, queryParameters, custom, ...rest } = endpoint;
return {
...rest,
custom: {
Expand All @@ -27,7 +29,7 @@ export function defineEndpoint(endpoint: DocumentedEndpoint): Endpoint {
description,
responseSchema,
errorResponseSchemas,
queryParamters,
queryParameters,
},
},
};
Expand Down
7 changes: 5 additions & 2 deletions packages/openapi/src/payload-config/routes/custom-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Endpoint, SanitizedConfig } from 'payload/config';
import { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/types';
import { createResponse } from '../../schemas';
import { getEndpointDocumentation } from '../../config-extensions';
import { objectEntries } from 'ts-powertools';

type Config = SanitizedConfig | SanitizedCollectionConfig | SanitizedGlobalConfig;
type ConfigType = 'payload' | 'global' | 'collection';
Expand Down Expand Up @@ -88,13 +89,15 @@ export const getCustomPaths = (config: Config, type: ConfigType): Pick<Required<
const tags = getTags(config, type);

for (const endpoint of config.endpoints.filter(endpoint => isRelevant(endpoint, type))) {
if (endpoint.custom?.openapi === false) continue;

const { path, parameters = [] } = getPath(basePath, endpoint.path);
const {
summary,
description = 'custom operation',
responseSchema = { type: 'object' },
errorResponseSchemas = {},
queryParamters = [],
queryParameters = {},
} = getEndpointDocumentation(endpoint) || {};
if (!paths[path]) paths[path] = {};

Expand All @@ -104,7 +107,7 @@ export const getCustomPaths = (config: Config, type: ConfigType): Pick<Required<
tags,
parameters: [
...parameters,
...queryParamters.map(({ name, description, required, schema }) => ({
...objectEntries(queryParameters).map(([name, { description, required, schema }]) => ({
name,
description: description || name,
in: 'query',
Expand Down
8 changes: 8 additions & 0 deletions packages/openapi/test/integration/custom-endpoints.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ const config: Config = {
res.json(value);
},
}),
{
path: '/hidden',
method: 'get',
handler: () => {
/* do nothing */
},
custom: { openapi: false },
},
],
},
],
Expand Down
4 changes: 4 additions & 0 deletions packages/swagger/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.3.0

- Support for custom endpoint documentation of payload-openapi

## 1.2.1

- Adds fallback document if openapi document generation fails
Expand Down
76 changes: 76 additions & 0 deletions packages/swagger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,82 @@ const Media: CollectionConfig = {
};
```

## Documentation of custom endpoints

Custom endpoints on all levels can be fully documented, either by using the `defineEndpoint` helper:

```ts
import { CollectionConfig } from 'payload/types';
import { defineEndpoint } from 'payload-swagger';

const Post: CollectionConfig = {
slug: 'posts',
endpoints: [
defineEndpoint({
summary: 'media by category',
description: 'gets the media of the given category',
path: '/category/:category',
method: 'get',
responseSchema: 'posts',
errorResponseSchemas: {
404: 'error',
},
queryParameters: {
limit: { schema: { type: 'number' } },
page: { schema: { type: 'number' } },
sort: { scema: { type: 'string' } },
},
handler: (req, res) => {
// ... handler implementation
},
}),
],
// ... Rest of collection config
};
```

or by setting the `custom.openapi` property:

```ts
import { CollectionConfig } from 'payload/types';
import type { EndpointDocumentation } from 'payload-swagger';

const documentation: EndpointDocumentation = {
summary: 'media by category',
description: 'gets the media of the given category',
responseSchema: 'posts',
errorResponseSchemas: {
404: { type: 'object', properties: { message: { type: 'string' } }, required: ['message'] },
},
queryParameters: {
limit: { schema: { type: 'number' } },
page: { schema: { type: 'number' } },
sort: { scema: { type: 'string' } },
},
};

const Post: CollectionConfig = {
slug: 'posts',
endpoints: [
{
path: '/category/:category',
method: 'get',
handler: (req, res) => {
// ... handler implementation
},
custom: {
openapi: documentation,
},
},
],
// ... Rest of collection config
};
```

Response schemas can be defined as openapi schema objects or as string, referencing any of the schemas defined in the schema section of the openapi document.

To exclude a custom endpoint from the documentation, set the `custom.openapi` property to `false`.

## Excluding unused endpoints

In Payload `collections` and `globals` have a standard set of available endpoints. In some situations you might not want to use some of these endpoints. In those situations you probably have used an access method that looks something like this: `() => false;`. This blocks all traffic, but the endpoint is still part of the openapi documentation.
Expand Down
13 changes: 5 additions & 8 deletions packages/testbed/src/collections/Posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,20 @@ const Posts: CollectionConfig = {
errorResponseSchemas: {
404: 'error',
},
queryParamters: [
{
name: 'limit',
queryParameters: {
limit: {
description: 'limit the number of posts returned',
schema: { type: 'number' },
},
{
name: 'page',
page: {
description: 'the page number to return',
schema: { type: 'number' },
},
{
name: 'sort',
sort: {
description: 'the field to sort by',
schema: { type: 'string' },
},
],
},
handler: async (req, res) => {
const { docs } = await req.payload.find<any>({
collection: 'categories',
Expand Down

0 comments on commit aba3b90

Please sign in to comment.