mirror of
https://github.com/coder/coder.git
synced 2026-10-29 17:44:33 +08:00
feat: Build framework for generating API docs (#5383)
* WIP * Gen * WIP * chi swagger * WIP * WIP * WIP * GetWorkspaces * GetWorkspaces * Markdown * Use widdershins * WIP * WIP * WIP * Markdown template * Fix: makefile * fmt * Fix: comment * Enable swagger conditionally * fix: site * Default false * Flag tests * fix * fix * template fixes * Fix * Fix * Fix * WIP * Formatted * Cleanup * Templates * BEGIN END SECTION * subshell exit code * Fix * Fix merge * WIP * Fix * Fix fmt * Fix * Generic api.md page * Fix merge * Link pages * Fix * Fix * Fix: links * Add icon * Write manifest file * Fix fmt * Fix: enterprise * Fix: Swagger.Enable * Fix: rename apidocs to apidoc * Fix: find -not -prune * Fix: json not available * Fix: rename Coderd API to Coder API * Fix: npm exec * Fix: api dir * Fix: by ID * Fix: string uuid * Fix: include deleted * Fix: indirect go.mod * Fix: source lib.sh * Fix: shellcheck * Fix: pushd popd * Fix: fmt * Fix: improve workspaces * Fix: swagger-enable * Fix * Fix: mention only HTTP 200 * Fix: IDs * Fix: https * Fix: icon * More APis * Fix: format swagger.json * Fix: SwaggerEndpoint * Fix: SCRIPT_DIR * Fix: PROJECT_ROOT * Fix: use code tags in schemas.md * Fix: examples * Fix: examples * Fix: improve format * Fix: date-time,enums * Fix: include_deleted * Fix: array of * Fix: parameter, response * Fix: string time or null * Workspaces: more docs * Workspaces: more docs * Fix: renderDisplayName * Fix: ActiveUserCount * Fix * Fix: typo * Templates: docs * Notice: incomplete
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
## Swagger / OpenAPI 2 and OpenAPI 3 template parameters
|
||||
|
||||
Note that properties of OpenAPI objects will be in OpenAPI 3.0 form, as
|
||||
Swagger / OpenAPI 2.0 definitions are converted automatically.
|
||||
|
||||
### Code templates
|
||||
|
||||
* `method` - the HTTP method of the operation (in lower-case)
|
||||
* `methodUpper` - the HTTP method of the operation (in upper-case)
|
||||
* `url` - the full URL of the operation (including protocol and host)
|
||||
* `consumes[]` - an array of MIME-types the operation consumes
|
||||
* `produces[]` - an array of MIME-types the operation produces
|
||||
* `operation` - the current operation object
|
||||
* `operationId` - the current operation id
|
||||
* `opName` - the operationId if set, otherwise the method + path
|
||||
* `tags[]` - the full list of tags applying to the operation
|
||||
* `security` - the security definitions applying to the operation
|
||||
* `resource` - the current tag/path object
|
||||
* `parameters[]` - an array of parameters for the operation (see below)
|
||||
* `queryString` - an example queryString, urlEncoded
|
||||
* `requiredQueryString` - an example queryString for `required:true` parameters
|
||||
* `queryParameters[]` - a subset of `parameters` that are `in:query`
|
||||
* `requiredParameters[]` - a subset of `queryParameters` that are `required:true`
|
||||
* `headerParameters[]` - a subset of `parameters` that are `in:header`
|
||||
* `allHeaders[]` - a concatenation of `headerParameters` and pseudo-parameters `Accept` and `Content-Type`, and optionally `Authorization` (the latter has an `isAuth` boolean property set true so it can be omitted in templates if desired
|
||||
|
||||
### Parameter template
|
||||
|
||||
* `parameters[]` - an array of [parameters](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#parameterObject), including the following pseudo-properties
|
||||
* `shortDesc` - a truncated version of the parameter description
|
||||
* `safeType` - a computed version of the parameter type, including Body and schema names
|
||||
* `originalType` - the original type of the parameter
|
||||
* `exampleValues` - an object containing examples for use in code-templates
|
||||
* `json` - example values in JSON compatible syntax
|
||||
* `object` - example values in raw object form (unquoted strings etc)
|
||||
* `depth` - a zero-based indicator of the depth of expanded request body parameters
|
||||
* `enums[]` - an array of (parameter)name/value pairs
|
||||
|
||||
### Responses template
|
||||
|
||||
* `responses[]` - an array of [responses](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#responseObject), including `status` and `meaning` properties
|
||||
|
||||
### Authentication template
|
||||
|
||||
* `authenticationStr` - a simple string of methods (and scopes where appropriate)
|
||||
* `securityDefinitions[]` - an array of applicable [securityDefinitions](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securityRequirementObject)
|
||||
|
||||
### Schema Property template
|
||||
|
||||
* `schemaProperties[]` - an array of
|
||||
* `name`
|
||||
* `type`
|
||||
* `required`
|
||||
* `description`
|
||||
* `enums[]` - an array of (schema property)name/value pairs
|
||||
|
||||
### Common to all templates
|
||||
|
||||
* `openapi` - the top-level OpenAPI / Swagger document
|
||||
* `header` - the front-matter of the Slate/Shins markdown document
|
||||
* `host` - the (computed) host of the API
|
||||
* `protocol` - the default/first protocol of the API
|
||||
* `baseUrl` - the (computed) baseUrl of the API (including protocol and host)
|
||||
* `widdershins` - the contents of widdershins `package.json`
|
||||
@@ -0,0 +1 @@
|
||||
To perform this operation, you must be authenticated by means of one of the following methods: **{{= data.utils.getAuthenticationStr(data) }}**.
|
||||
@@ -0,0 +1,4 @@
|
||||
# Example request using curl
|
||||
curl -X {{=data.methodUpper}} http://coder-server:8080{{=data.url}}{{=data.requiredQueryString}}{{?data.allHeaders.length}} \{{?}}
|
||||
{{~data.allHeaders :p:index}} -H '{{=p.name}}: {{=p.exampleValues.object}}'{{?index < data.allHeaders.length-1}} \{{?}}
|
||||
{{~}}
|
||||
@@ -0,0 +1 @@
|
||||
{{= data.utils.inspect(data) }}
|
||||
@@ -0,0 +1,157 @@
|
||||
{{
|
||||
function renderSinglePropertyType(p) {
|
||||
if (!p.$ref) {
|
||||
return p.type;
|
||||
}
|
||||
|
||||
const pRef = p.$ref.replace("#/components/schemas/","");
|
||||
if (pRef == "codersdk.NullTime") {
|
||||
return "string(time) or `null`";
|
||||
}
|
||||
return "[" + pRef + "](#" + pRef.replace(".","").toLowerCase() + ")";
|
||||
}
|
||||
|
||||
function renderPropertyType(p) {
|
||||
if (p.type == "array") {
|
||||
return "array of " + renderSinglePropertyType(p.schema.items);
|
||||
}
|
||||
return renderSinglePropertyType(p);
|
||||
}
|
||||
|
||||
function renderDisplayName(p) {
|
||||
if (p.displayName == "» **additionalProperties**") {
|
||||
return "» `[any property]`";
|
||||
}
|
||||
if (p.displayName == "**additionalProperties**") {
|
||||
return "`[any property]`";
|
||||
}
|
||||
return "`" + p.displayName + "`";
|
||||
}
|
||||
|
||||
function renderDescription(p) {
|
||||
if (!p.description) {
|
||||
return "none";
|
||||
}
|
||||
|
||||
const toSnakeCase = str =>
|
||||
str
|
||||
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
|
||||
.map(x => x.toLowerCase())
|
||||
.join('_');
|
||||
|
||||
const words = p.description.split(' ');
|
||||
if (words.length == 0) {
|
||||
return "none";
|
||||
}
|
||||
|
||||
const countUppercase = words[0].length - words[0].replace(/[A-Z]/g, '').length;
|
||||
if (countUppercase > 1) {
|
||||
const displayName = p.displayName.charAt(0).toUpperCase() + p.displayName.replaceAll("_", " ").toLowerCase().slice(1);
|
||||
return displayName + " " + words.slice(1).join(' ');
|
||||
}
|
||||
return p.description;
|
||||
}
|
||||
}}
|
||||
|
||||
{{? data.api.components && data.api.components.securitySchemes }}{{#def.security}}{{?}}
|
||||
|
||||
{{ for (var r in data.resources) { }}
|
||||
{{ data.resource = data.resources[r]; }}
|
||||
|
||||
<!-- APIDOCGEN: BEGIN SECTION -->
|
||||
{{= data.tags.section }}# {{= r}}
|
||||
|
||||
> This page is incomplete, stay tuned.
|
||||
|
||||
{{? data.resource.description }}{{= data.resource.description}}{{?}}
|
||||
|
||||
{{ for (var m in data.resource.methods) { }}
|
||||
{{ data.operationUniqueName = m; }}
|
||||
{{ data.method = data.resource.methods[m]; }}
|
||||
{{ data.operationUniqueSlug = data.method.slug; }}
|
||||
{{ data.operation = data.method.operation; }}
|
||||
{{= data.templates.operation(data) }}
|
||||
{{ } /* of methods */ }}
|
||||
|
||||
{{= data.tags.endSection }}
|
||||
{{ } /* of resources */ }}
|
||||
|
||||
{{? data.api.components && data.api.components.schemas }}
|
||||
{{= data.tags.section }}
|
||||
|
||||
<!-- APIDOCGEN: BEGIN SECTION -->
|
||||
# Schemas
|
||||
|
||||
> This page is incomplete, stay tuned.
|
||||
|
||||
{{ for (var s in data.components.schemas) {
|
||||
if (s == "codersdk.NullTime") {
|
||||
continue;
|
||||
}
|
||||
}}
|
||||
{{ var origSchema = data.components.schemas[s]; }}
|
||||
{{ var schema = data.api.components.schemas[s]; }}
|
||||
|
||||
{{= data.tags.section }}
|
||||
## {{=s}}
|
||||
|
||||
{{? data.options.yaml }}
|
||||
```yaml
|
||||
{{=data.utils.yaml.stringify(data.utils.getSample(schema,data.options,{quiet:true},data.api))}}
|
||||
{{??}}
|
||||
```json
|
||||
{{=data.utils.safejson(data.utils.getSample(schema,data.options,{quiet:true},data.api),null,2)}}
|
||||
{{?}}```
|
||||
|
||||
{{ var enums = []; }}
|
||||
{{ var blocks = data.utils.schemaToArray(origSchema,-1,{trim:true,join:true},data); }}
|
||||
{{ for (var block of blocks) {
|
||||
for (var p of block.rows) {
|
||||
if (p.schema && p.schema.enum) {
|
||||
for (var e of p.schema.enum) {
|
||||
enums.push({name:p.name,value:e});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
{{~ blocks :block}}
|
||||
{{? block.title }}{{= block.title}}{{= '\n\n'}}{{?}}
|
||||
{{? block.externalDocs}}
|
||||
<a href="{{=block.externalDocs.url}}">{{=block.externalDocs.description||'External documentation'}}</a>
|
||||
{{?}}
|
||||
|
||||
{{? block===blocks[0] }}
|
||||
{{= data.tags.section }}
|
||||
|
||||
### Properties
|
||||
{{?}}
|
||||
|
||||
{{? block.rows.length}}|Name|Type|Required|Restrictions|Description|
|
||||
|---|---|---|---|---|{{?}}
|
||||
{{~ block.rows :p}}|{{= renderDisplayName(p)}}|{{= renderPropertyType(p)}}|{{=p.required}}|{{=p.restrictions||'none'}}|{{= renderDescription(p)}}|
|
||||
{{~}}
|
||||
{{~}}
|
||||
{{? (blocks[0].rows.length === 0) && (blocks.length === 1) }}
|
||||
*None*
|
||||
{{?}}
|
||||
|
||||
{{? enums.length > 0 }}
|
||||
{{= data.tags.section }}
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
|Property|Value|
|
||||
|---|---|
|
||||
{{~ enums :e}}|{{=e.name}}|{{=data.utils.toPrimitive(e.value)}}|
|
||||
{{~}}
|
||||
|
||||
{{= data.tags.endSection }}
|
||||
{{?}}
|
||||
|
||||
{{= data.tags.endSection }}
|
||||
{{= data.tags.endSection }}
|
||||
{{ } /* of schemas */ }}
|
||||
|
||||
{{?}}
|
||||
@@ -0,0 +1,48 @@
|
||||
{{= data.tags.section }}
|
||||
|
||||
## {{= data.operationUniqueName}}
|
||||
|
||||
{{ data.methodUpper = data.method.verb.toUpperCase(); }}
|
||||
{{ data.url = data.utils.slashes(data.baseUrl + data.method.path); }}
|
||||
{{ data.parameters = data.operation.parameters; }}
|
||||
{{ data.enums = []; }}
|
||||
{{ data.utils.fakeProdCons(data); }}
|
||||
{{ data.utils.fakeBodyParameter(data); }}
|
||||
{{ data.utils.mergePathParameters(data); }}
|
||||
{{ data.utils.getParameters(data); }}
|
||||
|
||||
{{? data.options.codeSamples || data.operation["x-code-samples"] }}
|
||||
### Code samples
|
||||
|
||||
{{= data.utils.getCodeSamples(data)}}
|
||||
{{?}}
|
||||
|
||||
`{{= data.methodUpper}} {{=data.method.path}}`
|
||||
|
||||
{{? data.operation.summary && !data.options.tocSummary}}*{{= data.operation.summary }}*{{?}}
|
||||
|
||||
{{? data.operation.description}}{{= data.operation.description }}{{?}}
|
||||
|
||||
{{? data.operation.requestBody}}
|
||||
> Body parameter
|
||||
|
||||
{{? data.bodyParameter.exampleValues.description }}
|
||||
> {{= data.bodyParameter.exampleValues.description }}
|
||||
{{?}}
|
||||
|
||||
{{= data.utils.getBodyParameterExamples(data) }}
|
||||
{{?}}
|
||||
|
||||
{{? data.parameters && data.parameters.length }}
|
||||
{{#def.parameters}}
|
||||
{{?}}
|
||||
|
||||
{{#def.responses}}
|
||||
|
||||
{{ data.security = data.operation.security ? data.operation.security : data.api.security; }}
|
||||
{{? data.security && data.security.length }}
|
||||
{{#def.authentication}}
|
||||
{{??}}
|
||||
{{#def.authentication_none}}
|
||||
{{?}}
|
||||
{{= data.tags.endSection }}
|
||||
@@ -0,0 +1,50 @@
|
||||
{{
|
||||
function renderParameterType(p) {
|
||||
if (p.schema['x-widdershins-oldRef']) {
|
||||
const aType = p.schema['x-widdershins-oldRef'].replace("#/components/schemas/","");
|
||||
const href = aType.replace(".","").toLowerCase();
|
||||
return "[" + aType + "](schemas.md#" + href + ")";
|
||||
}
|
||||
return p.safeType;
|
||||
}
|
||||
}}
|
||||
{{= data.tags.section }}
|
||||
### Parameters
|
||||
|
||||
|Name|In|Type|Required|Description|
|
||||
|---|---|---|---|---|
|
||||
{{~ data.parameters :p}}|{{=p.name}}|{{=p.in}}|{{= renderParameterType(p)}}|{{=p.required}}|{{=p.shortDesc || 'none'}}|
|
||||
{{~}}
|
||||
|
||||
{{? data.longDescs }}
|
||||
#### Detailed descriptions
|
||||
{{~ data.parameters :p}}{{? p.shortDesc !== p.description}}
|
||||
**{{=p.name}}**: {{=p.description}}{{?}}
|
||||
{{~}}
|
||||
{{?}}
|
||||
|
||||
{{~ data.parameters :p}}
|
||||
|
||||
{{? p.schema && p.schema.enum }}
|
||||
{{~ p.schema.enum :e}}
|
||||
{{ var entry = {}; entry.name = p.name; entry.value = e; data.enums.push(entry); }}
|
||||
{{~}}
|
||||
{{?}}
|
||||
|
||||
{{? p.schema && p.schema.items && p.schema.items.enum }}
|
||||
{{~ p.schema.items.enum :e}}
|
||||
{{ var entry = {}; entry.name = p.name; entry.value = e; data.enums.push(entry); }}
|
||||
{{~}}
|
||||
{{?}}
|
||||
|
||||
{{~}}
|
||||
|
||||
{{? data.enums && data.enums.length }}
|
||||
#### Enumerated Values
|
||||
|
||||
|Parameter|Value|
|
||||
|---|---|
|
||||
{{~ data.enums :e}}|{{=e.name}}|{{=data.utils.toPrimitive(e.value)}}|
|
||||
{{~}}
|
||||
{{?}}
|
||||
{{= data.tags.endSection }}
|
||||
@@ -0,0 +1,113 @@
|
||||
{{
|
||||
function renderSingleResponseType(r) {
|
||||
var content;
|
||||
for (var ct in r.content) {
|
||||
content = r.content[ct];
|
||||
break;
|
||||
}
|
||||
if (!content) {
|
||||
return "no schema";
|
||||
}
|
||||
|
||||
var ref = content.schema["x-widdershins-oldRef"];
|
||||
if (!ref) {
|
||||
ref = content.schema.items["x-widdershins-oldRef"];
|
||||
}
|
||||
const aType = ref.replace("#/components/schemas/","");
|
||||
const href = aType.replace(".","").toLowerCase();
|
||||
return "[" + aType + "](schemas.md#" + href + ")";
|
||||
}
|
||||
|
||||
function renderResponseType(r) {
|
||||
if (r.type == "array") {
|
||||
return "array of " + renderSingleResponseType(r);
|
||||
}
|
||||
return renderSingleResponseType(r);
|
||||
}
|
||||
}}
|
||||
{{ data.responses = data.utils.getResponses(data); }}
|
||||
{{ data.responseSchemas = false; }}
|
||||
{{~ data.responses :response }}
|
||||
{{ if (response.content) data.responseSchemas = true; }}
|
||||
{{~}}
|
||||
|
||||
{{? data.responseSchemas }}
|
||||
### Example responses
|
||||
|
||||
{{= data.utils.getResponseExamples(data) }}
|
||||
{{?}}
|
||||
|
||||
{{= data.tags.section }}
|
||||
### Responses
|
||||
|
||||
|Status|Meaning|Description|Schema|
|
||||
|---|---|---|---|
|
||||
{{~ data.responses :r}}|{{=r.status}}|{{=r.meaning}}|{{=r.description || 'none'}}|{{= renderResponseType(r)}}|
|
||||
{{~}}
|
||||
|
||||
{{ data.responseSchemas = false; }}
|
||||
{{~ data.responses :response }}
|
||||
{{ if (response.content && !response.$ref && !data.utils.isPrimitive(response.type)) data.responseSchemas = true; }}
|
||||
{{~}}
|
||||
{{? data.responseSchemas }}
|
||||
<h3 id="{{=data.operationUniqueSlug}}-responseschema">Response Schema</h3>
|
||||
{{~ data.responses :response}}
|
||||
{{? response.content && !response.$ref && !data.utils.isPrimitive(response.type)}}
|
||||
{{? Object.keys(response.content).length }}
|
||||
{{ var responseKey = Object.keys(response.content)[0]; }}
|
||||
{{ var responseSchema = response.content[responseKey].schema; }}
|
||||
{{ var enums = []; }}
|
||||
{{ var blocks = data.utils.schemaToArray(responseSchema,0,{trim:true,join:true},data); }}
|
||||
{{ for (var block of blocks) {
|
||||
for (var p of block.rows) {
|
||||
if (p.schema && p.schema.enum) {
|
||||
for (var e of p.schema.enum) {
|
||||
enums.push({name:p.name,value:e});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
{{? blocks[0].rows.length || blocks[0].title }}
|
||||
Status Code **{{=response.status}}**
|
||||
|
||||
{{~ blocks :block}}
|
||||
{{? block.title }}*{{=block.title}}*
|
||||
{{?}}
|
||||
|Name|Type|Required|Restrictions|Description|
|
||||
|---|---|---|---|---|
|
||||
{{~block.rows :p}}|{{=p.displayName}}|{{? p.$ref}}`{{=p.$ref}}`{{?}}{{? !p.$ref}}{{=p.type}}{{?}}|{{=p.required}}|{{=p.restrictions||'none'}}|{{=p.description||'none'}}|
|
||||
{{~}}
|
||||
{{~}}
|
||||
{{?}}
|
||||
|
||||
{{? enums.length > 0 }}
|
||||
#### Enumerated Values
|
||||
|
||||
|Property|Value|
|
||||
|---|---|
|
||||
{{~ enums :e}}|{{=e.name}}|{{=data.utils.toPrimitive(e.value)}}|
|
||||
{{~}}
|
||||
|
||||
{{?}}
|
||||
{{?}}
|
||||
|
||||
{{ data.response = response; }}
|
||||
|
||||
{{?}}
|
||||
{{~}}
|
||||
{{?}}
|
||||
|
||||
{{ data.responseHeaders = data.utils.getResponseHeaders(data); }}
|
||||
{{? data.responseHeaders.length }}
|
||||
|
||||
### Response Headers
|
||||
|
||||
|Status|Header|Type|Format|Description|
|
||||
|---|---|---|---|---|
|
||||
{{~ data.responseHeaders :h}}|{{=h.status}}|{{=h.header}}|{{=h.type}}|{{=h.format||''}}|{{=h.description||'none'}}|
|
||||
{{~}}
|
||||
|
||||
{{?}}
|
||||
{{= data.tags.endSection }}
|
||||
@@ -0,0 +1,27 @@
|
||||
<!-- APIDOCGEN: BEGIN SECTION -->
|
||||
{{= data.tags.section }}# Authentication
|
||||
{{ for (var s in data.api.components.securitySchemes) { }}
|
||||
{{ var sd = data.api.components.securitySchemes[s]; }}
|
||||
{{? sd.type == 'apiKey' }}
|
||||
- API Key ({{=s}})
|
||||
- Parameter Name: **{{=sd.name}}**, in: {{=sd.in}}. {{=sd.description || ''}}
|
||||
{{?}}
|
||||
{{? sd.type == 'http'}}
|
||||
- HTTP Authentication, scheme: {{=sd.scheme}}{{? sd.description }}<br/>{{=sd.description}}{{?}}
|
||||
{{?}}
|
||||
{{? sd.type == 'oauth2'}}
|
||||
- oAuth2 authentication. {{=sd.description || ''}}
|
||||
{{ for (var f in sd.flows) { }}
|
||||
{{ var flow = sd.flows[f]; }}
|
||||
- Flow: {{=f}}
|
||||
{{? flow.authorizationUrl}} - Authorization URL = [{{=flow.authorizationUrl}}]({{=flow.authorizationUrl}}){{?}}
|
||||
{{? flow.tokenUrl}} - Token URL = [{{=flow.tokenUrl}}]({{=flow.tokenUrl}}){{?}}
|
||||
{{? flow.scopes && Object.keys(flow.scopes).length}}
|
||||
|Scope|Scope Description|
|
||||
|---|---|
|
||||
{{ for (var sc in flow.scopes) { }}|{{=sc}}|{{=data.utils.join(flow.scopes[sc])}}|
|
||||
{{ } /* of scopes */ }}
|
||||
{{?}}
|
||||
{{ } /* of flows */ }}
|
||||
{{?}}
|
||||
{{ } /* of securitySchemes */ }}
|
||||
Reference in New Issue
Block a user