diff --git a/.eslintrc b/.eslintrc
deleted file mode 100644
index ca53e9e..0000000
--- a/.eslintrc
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "extends": ["@diplodoc/eslint-config", "@diplodoc/eslint-config/prettier"],
- "root": true,
- "overrides": [
- {
- "files": ["*.ts", "*.tsx"],
- "parser": "@typescript-eslint/parser",
- "parserOptions": {
- "sourceType": "module",
- "project": ["./tsconfig.test.json", "./tsconfig.json"]
- }
- }
- ]
-}
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..8a04cd4
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,15 @@
+module.exports = {
+ extends: ['@diplodoc/eslint-config', '@diplodoc/eslint-config/prettier'],
+ root: true,
+ overrides: [
+ {
+ files: ['*.ts', '*.tsx'],
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ sourceType: 'module',
+ project: ['./tsconfig.test.json', './tsconfig.json'],
+ tsconfigRootDir: __dirname,
+ },
+ },
+ ],
+};
diff --git a/package-lock.json b/package-lock.json
index 77490d9..276bbc8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,7 @@
"version": "1.4.0",
"dependencies": {
"@apidevtools/swagger-parser": "^10.1.0",
- "@diplodoc/transform": "^4.2.1",
+ "@diplodoc/transform": "^4.3.0",
"bem-cn-lite": "^4.1.0",
"html-escaper": "^3.0.3",
"http-status-codes": "^2.2.0",
@@ -22,6 +22,7 @@
"@babel/core": "^7.23.2",
"@babel/eslint-parser": "^7.22.15",
"@diplodoc/eslint-config": "^1.0.15",
+ "@diplodoc/prettier-config": "^1.0.0",
"@diplodoc/tsconfig": "^1.0.2",
"@gravity-ui/uikit": "^5.18.0",
"@swc/cli": "^0.1.62",
@@ -44,6 +45,7 @@
"markdown-it": "^13.0.1",
"npm-run-all": "^4.1.5",
"openapi-types": "^12.1.3",
+ "prettier": "^3.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ts-jest": "^29.1.1",
@@ -1010,6 +1012,15 @@
"eslint-plugin-security": "1.7.1"
}
},
+ "node_modules/@diplodoc/prettier-config": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@diplodoc/prettier-config/-/prettier-config-1.0.0.tgz",
+ "integrity": "sha512-g4dShJe/sRPTorvBX0VnTQj38dvTHRVdAB6wxuSU3lEpR4jUl/kKYShvKwAYZFhNmERj0dXPV0DJFr3krI4K/Q==",
+ "dev": true,
+ "peerDependencies": {
+ "prettier": "*"
+ }
+ },
"node_modules/@diplodoc/tabs-extension": {
"version": "2.0.12",
"resolved": "https://registry.npmjs.org/@diplodoc/tabs-extension/-/tabs-extension-2.0.12.tgz",
@@ -1024,9 +1035,9 @@
}
},
"node_modules/@diplodoc/transform": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@diplodoc/transform/-/transform-4.2.1.tgz",
- "integrity": "sha512-e9rU5Sdoe9ntdDn3vRNrgJ9/NqG5Vu6PHoiqEhIRcnK/x2Tp/GqUgZYcU5CLKNupx0SMqfv4GgGNzg+uJiWzXQ==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@diplodoc/transform/-/transform-4.3.0.tgz",
+ "integrity": "sha512-68TwLtZtWqBbEelN4CJ0Y6Sg8ujIxUbxrtmuvplLwAJu/KJv+3z4A+N2JAWUvTmS1BCoyEeYyVtILJk0jT+ubw==",
"dependencies": {
"@diplodoc/tabs-extension": "2.0.12",
"chalk": "4.1.2",
@@ -1044,7 +1055,7 @@
"markdown-it-sup": "1.0.0",
"markdownlint": "^0.25.1",
"markdownlint-rule-helpers": "0.17.2",
- "sanitize-html": "2.7.3",
+ "sanitize-html": "^2.11.0",
"slugify": "1.6.5"
},
"peerDependencies": {
@@ -10152,9 +10163,9 @@
"dev": true
},
"node_modules/nanoid": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
- "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"funding": [
{
"type": "github",
@@ -10848,7 +10859,6 @@
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
"dev": true,
- "peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -11615,66 +11625,18 @@
}
},
"node_modules/sanitize-html": {
- "version": "2.7.3",
- "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.7.3.tgz",
- "integrity": "sha512-jMaHG29ak4miiJ8wgqA1849iInqORgNv7SLfSw9LtfOhEUQ1C0YHKH73R+hgyufBW9ZFeJrb057k9hjlfBCVlw==",
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.11.0.tgz",
+ "integrity": "sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA==",
"dependencies": {
"deepmerge": "^4.2.2",
"escape-string-regexp": "^4.0.0",
- "htmlparser2": "^6.0.0",
+ "htmlparser2": "^8.0.0",
"is-plain-object": "^5.0.0",
"parse-srcset": "^1.0.2",
"postcss": "^8.3.11"
}
},
- "node_modules/sanitize-html/node_modules/dom-serializer": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
- "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
- "dependencies": {
- "domelementtype": "^2.0.1",
- "domhandler": "^4.2.0",
- "entities": "^2.0.0"
- },
- "funding": {
- "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
- }
- },
- "node_modules/sanitize-html/node_modules/domhandler": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
- "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
- "dependencies": {
- "domelementtype": "^2.2.0"
- },
- "engines": {
- "node": ">= 4"
- },
- "funding": {
- "url": "https://github.com/fb55/domhandler?sponsor=1"
- }
- },
- "node_modules/sanitize-html/node_modules/domutils": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
- "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
- "dependencies": {
- "dom-serializer": "^1.0.1",
- "domelementtype": "^2.2.0",
- "domhandler": "^4.2.0"
- },
- "funding": {
- "url": "https://github.com/fb55/domutils?sponsor=1"
- }
- },
- "node_modules/sanitize-html/node_modules/entities": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
- "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
"node_modules/sanitize-html/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -11686,24 +11648,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/sanitize-html/node_modules/htmlparser2": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
- "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==",
- "funding": [
- "https://github.com/fb55/htmlparser2?sponsor=1",
- {
- "type": "github",
- "url": "https://github.com/sponsors/fb55"
- }
- ],
- "dependencies": {
- "domelementtype": "^2.0.1",
- "domhandler": "^4.0.0",
- "domutils": "^2.5.2",
- "entities": "^2.0.0"
- }
- },
"node_modules/sass": {
"version": "1.63.6",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.63.6.tgz",
diff --git a/package.json b/package.json
index 7a53d04..87913b3 100644
--- a/package.json
+++ b/package.json
@@ -89,7 +89,7 @@
},
"dependencies": {
"@apidevtools/swagger-parser": "^10.1.0",
- "@diplodoc/transform": "^4.2.1",
+ "@diplodoc/transform": "^4.3.0",
"bem-cn-lite": "^4.1.0",
"html-escaper": "^3.0.3",
"http-status-codes": "^2.2.0",
diff --git a/src/__snapshots__/combiners/complex.test.ts.snap b/src/__snapshots__/combiners/complex.test.ts.snap
index fe4fba2..aff298d 100644
--- a/src/__snapshots__/combiners/complex.test.ts.snap
+++ b/src/__snapshots__/combiners/complex.test.ts.snap
@@ -32,8 +32,7 @@ Generated server url{.openapi__request__description}
\`\`\`json
{
- "name": "b",
- "age": 0
+ "name": "b"
}
\`\`\`
@@ -45,9 +44,9 @@ Generated server url{.openapi__request__description}
|| name | string | Default: \`b\` ||
-|| age | number, boolean | ||
+|| age | any | ||
-|| ...rest | oneOf | [Dog](#dog)
+|| ...rest | oneOf | [Dog](#dog)
or [Cat](#cat) |||#
#### Or value from:
@@ -87,8 +86,7 @@ Base 200 response
\`\`\`json
{
- "name": "b",
- "age": 0
+ "name": "b"
}
\`\`\`
@@ -100,9 +98,9 @@ Base 200 response
|| name | string | Default: \`b\` ||
-|| age | number, boolean | ||
+|| age | any | ||
-|| ...rest | oneOf | [Dog](#dog)
+|| ...rest | oneOf | [Dog](#dog)
or [Cat](#cat) |||#
#### Or value from:
diff --git a/src/__snapshots__/combiners/oneOf.test.ts.snap b/src/__snapshots__/combiners/oneOf.test.ts.snap
index fa7813a..54aa780 100644
--- a/src/__snapshots__/combiners/oneOf.test.ts.snap
+++ b/src/__snapshots__/combiners/oneOf.test.ts.snap
@@ -31,7 +31,10 @@ Generated server url{.openapi__request__description}
\`\`\`json
-{}
+{
+ "type": "string",
+ "baz": "string"
+}
\`\`\`
@@ -40,7 +43,7 @@ Generated server url{.openapi__request__description}
#||| **Name** | **Type** | **Description** ||
-|| ...rest | oneOf | [Dog](#dog)
+|| ...rest | oneOf | [Dog](#dog)
or [Cat](#cat) |||#
#### Or value from:
@@ -77,7 +80,10 @@ Cat class
\`\`\`json
-{}
+{
+ "type": "string",
+ "baz": "string"
+}
\`\`\`
@@ -86,7 +92,8 @@ Cat class
#||| **Name** | **Type** | **Description** ||
-|| ...rest | oneOf | [Dog](#dog)
+|| ...rest | oneOf | Base 200 response
+[Dog](#dog)
or [Cat](#cat) |||#
#### Or value from:
@@ -144,7 +151,7 @@ Generated server url{.openapi__request__description}
|| age | number | ||
-|| ...rest | oneOf | [Dog](#dog)
+|| ...rest | oneOf | [Dog](#dog)
or [Cat](#cat) |||#
#### Or value from:
@@ -197,7 +204,8 @@ Cat class
|| age | number | ||
-|| ...rest | oneOf | [Dog](#dog)
+|| ...rest | oneOf | Base 200 response
+[Dog](#dog)
or [Cat](#cat) |||#
#### Or value from:
@@ -240,7 +248,10 @@ Generated server url{.openapi__request__description}
\`\`\`json
{
- "pet": {}
+ "pet": {
+ "type": "string",
+ "baz": "string"
+ }
}
\`\`\`
@@ -250,10 +261,10 @@ Generated server url{.openapi__request__description}
#||| **Name** | **Type** | **Description** ||
-|| pet | object | [Dog](#dog)
-or [Cat](#cat) ||
+|| pet | [Dog](#dog)
+or [Cat](#cat) | ||
-|| ...rest | oneOf | [Dog](#dog)
+|| ...rest | oneOf | [Dog](#dog)
or [Cat](#cat) |||#
#### Or value from:
@@ -291,7 +302,10 @@ Cat class
\`\`\`json
{
- "pet": {}
+ "pet": {
+ "type": "string",
+ "baz": "string"
+ }
}
\`\`\`
@@ -301,10 +315,11 @@ Cat class
#||| **Name** | **Type** | **Description** ||
-|| pet | object | [Dog](#dog)
-or [Cat](#cat) ||
+|| pet | [Dog](#dog)
+or [Cat](#cat) | ||
-|| ...rest | oneOf | [Dog](#dog)
+|| ...rest | oneOf | Base 200 response
+[Dog](#dog)
or [Cat](#cat) |||#
#### Or value from:
diff --git a/src/__snapshots__/description.test.ts.snap b/src/__snapshots__/description.test.ts.snap
index d2582ca..d0ef10b 100644
--- a/src/__snapshots__/description.test.ts.snap
+++ b/src/__snapshots__/description.test.ts.snap
@@ -36,7 +36,7 @@ Generated server url{.openapi__request__description}
{% cut "application/json" %}
-\`\`\`
+\`\`\`json
{
"pet": {
"type": "string",
diff --git a/src/includer/generators/index.ts b/src/includer/generators/index.ts
deleted file mode 100644
index 656426b..0000000
--- a/src/includer/generators/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import {main} from './main';
-import {endpoint, section} from '../ui';
-
-export {main, section, endpoint};
-
-export default {main, section, endpoint};
diff --git a/src/includer/generators/types.ts b/src/includer/generators/types.ts
deleted file mode 100644
index cf7cacc..0000000
--- a/src/includer/generators/types.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import stringify from 'json-stringify-safe';
-import RefsService from '../services/refs';
-
-import {
- JSONSchemaType,
- JSONSchemaUnionType,
- JsType,
- OpenJSONSchema,
- SupportedEnumType,
-} from '../models';
-
-import {SUPPORTED_ENUM_TYPES} from '../constants';
-
-function inferType(value: OpenJSONSchema): JSONSchemaType {
- if (value === null) {
- return 'null';
- }
-
- if (value.type) {
- return value.type;
- }
-
- if (value.enum) {
- const enumType = typeof value.enum[0];
- if (isSupportedEnumType(enumType)) {
- return enumType;
- }
-
- throw new Error(`Unsupported enum type in value: ${stringify(value)}`);
- }
-
- if (value.default) {
- const type = typeof value.default;
- if (isSupportedEnumType(type)) {
- return type;
- }
- }
-
- const ref = RefsService.find(value);
-
- if (value.oneOf?.length) {
- const unionOf = (value.oneOf.filter(Boolean) as OpenJSONSchema[]).flatMap(inferType);
-
- if (unionOf.length === 1) {
- return unionOf[0];
- }
-
- return {
- ref,
- unionOf,
- };
- }
-
- return 'any';
-}
-
-function isComplexType(type: JSONSchemaType): type is JSONSchemaUnionType {
- if (Array.isArray(type)) {
- return false;
- }
-
- if (typeof type !== 'object') {
- return false;
- }
-
- return (type.unionOf?.length || 0) > 1;
-}
-
-function typeToText(value: OpenJSONSchema): string {
- const type = inferType(value);
-
- if (isComplexType(type)) {
- const {unionOf} = type;
-
- if (unionOf) {
- return unionOf.join(' or ');
- }
-
- throw new Error(`Can not infer type of ${value}`);
- }
-
- return `${type}`;
-}
-
-function isSupportedEnumType(enumType: JsType): enumType is SupportedEnumType {
- return SUPPORTED_ENUM_TYPES.some((type) => enumType === type);
-}
-
-export {inferType, isComplexType, typeToText, isSupportedEnumType};
diff --git a/src/includer/index.ts b/src/includer/index.ts
index 3124222..2a662c3 100644
--- a/src/includer/index.ts
+++ b/src/includer/index.ts
@@ -1,5 +1,4 @@
import assert from 'assert';
-import AnonymousService from './services/anonymous';
import {dirname, join, resolve} from 'path';
import {mkdir, writeFile} from 'fs/promises';
@@ -8,7 +7,7 @@ import {matchFilter} from './utils';
import {dump} from 'js-yaml';
import parsers from './parsers';
-import generators from './generators';
+import generators from './ui';
import SwaggerParser from '@apidevtools/swagger-parser';
@@ -269,8 +268,6 @@ function handleEndpointIncluder(endpoint: Endpoint, pathPrefix: string, sandbox?
const path = join(pathPrefix, mdPath(endpoint));
const content = generators.endpoint(endpoint, sandbox);
- AnonymousService.clear();
-
return {path, content};
}
diff --git a/src/includer/models.ts b/src/includer/models.ts
index a781adb..8752964 100644
--- a/src/includer/models.ts
+++ b/src/includer/models.ts
@@ -1,4 +1,5 @@
import {JSONSchema6, JSONSchema6Definition} from 'json-schema';
+
import {
LeadingPageMode,
SPEC_RENDER_MODE_DEFAULT,
@@ -277,6 +278,7 @@ export type OpenApiFilter = {
};
export type OpenApiIncluderParams = {
+ allowAnonymousObjects?: boolean;
input: string;
leadingPage?: LeadingPageParams;
filter?: OpenApiFilter;
@@ -290,6 +292,7 @@ export type OpenApiIncluderParams = {
export type OpenJSONSchema = JSONSchema6 & {
_runtime?: true;
+ _emptyDescription?: true;
example?: unknown;
properties?: {
[key: string]: JSONSchema6Definition & {
@@ -299,10 +302,13 @@ export type OpenJSONSchema = JSONSchema6 & {
};
export type OpenJSONSchemaDefinition = OpenJSONSchema | boolean;
+export type FoundRefType = {
+ ref: string;
+};
export type BaseJSONSchemaType = Exclude;
export type JSONSchemaUnionType = {
ref?: string;
/* Not oneOf because of collision with OpenJSONSchema['oneOf'] */
unionOf?: JSONSchemaType[];
};
-export type JSONSchemaType = BaseJSONSchemaType | JSONSchemaUnionType;
+export type JSONSchemaType = BaseJSONSchemaType | JSONSchemaUnionType | FoundRefType;
diff --git a/src/includer/services/anonymous.ts b/src/includer/services/anonymous.ts
deleted file mode 100644
index 8b77bcf..0000000
--- a/src/includer/services/anonymous.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import {EOL} from '../constants';
-import {OpenJSONSchema, OpenJSONSchemaDefinition} from '../models';
-
-const pendingTables: Map = new Map();
-
-function add(ref: string | undefined, schema: OpenJSONSchemaDefinition) {
- if (typeof schema === 'boolean') {
- return;
- }
-
- if (!ref) {
- return;
- }
-
- pendingTables.set(ref, schema);
-}
-
-function render(): string {
- const entries = [...pendingTables.entries()];
-
- return entries.map(([, v]) => JSON.stringify(v, null, 2)).join(EOL);
-}
-
-function clear(): void {
- pendingTables.clear();
-}
-
-export {add, render, clear};
-export default {add, render, clear};
diff --git a/src/includer/services/refs.ts b/src/includer/services/refs.ts
index 374e077..941cdc4 100644
--- a/src/includer/services/refs.ts
+++ b/src/includer/services/refs.ts
@@ -1,10 +1,13 @@
+import {descriptionForOneOfElement} from '../traverse/types';
import {OpenJSONSchema, OpenJSONSchemaDefinition, Refs} from '../models';
-import {concatNewLine, descriptionForOneOfElement} from '../utils';
+import {concatNewLine} from '../utils';
+let allowRuntime: boolean;
let _refs: Refs = {};
-function init(allRefs: Refs) {
+function init(allRefs: Refs, allowAnonymousObjects?: boolean) {
_refs = allRefs;
+ allowRuntime = Boolean(allowAnonymousObjects);
}
/**
@@ -98,7 +101,7 @@ function merge(schema: OpenJSONSchemaDefinition, needToSaveRef = true): OpenJSON
if (value.oneOf?.length) {
const description = descriptionForOneOfElement(value);
- return {...value, description};
+ return {...value, description, _emptyDescription: true};
}
let description = value.description || '';
@@ -145,7 +148,7 @@ function removeInternalProperty(schema: OpenJSONSchema): OpenJSONSchema {
return schema;
}
-function get(ref: string): OpenJSONSchema {
+function get(ref: string): OpenJSONSchema | undefined {
return _refs[ref];
}
@@ -157,10 +160,18 @@ function has(name: string): boolean {
* To add runtime objects like anonymous oneOf
*/
function runtime(ref: string, value: OpenJSONSchema) {
+ if (!allowRuntime) {
+ return;
+ }
+
value._runtime = true;
_refs[ref] = value;
}
+function isRuntimeAllowed() {
+ return allowRuntime;
+}
+
function refs() {
return _refs;
}
@@ -173,4 +184,5 @@ export default {
runtime,
refs,
merge,
+ isRuntimeAllowed,
};
diff --git a/src/includer/generators/traverse.ts b/src/includer/traverse/tables.ts
similarity index 80%
rename from src/includer/generators/traverse.ts
rename to src/includer/traverse/tables.ts
index 3357863..7642cb7 100644
--- a/src/includer/generators/traverse.ts
+++ b/src/includer/traverse/tables.ts
@@ -1,10 +1,17 @@
import RefsService from '../services/refs';
+import stringify from 'json-stringify-safe';
import {anchor, table, tableParameterName, title} from '../ui';
-import {concatNewLine, descriptionForOneOfElement, extractOneOfElements} from '../utils';
+import {concatNewLine} from '../utils';
import {EOL} from '../constants';
import {OpenJSONSchema, OpenJSONSchemaDefinition} from '../models';
-import {inferType, isComplexType, typeToText} from './types';
+import {
+ descriptionForOneOfElement,
+ extractOneOfElements,
+ extractRefFromType,
+ inferType,
+ typeToText,
+} from './types';
type TableRow = [string, string, string];
@@ -20,10 +27,13 @@ export function tableFromSchema(schema: OpenJSONSchema): TableFromSchemaResult {
if (schema.enum) {
// enum description will be in table description
const description = prepareComplexDescription('', schema);
+ const type = inferType(schema);
+
const content = table([
['Type', 'Description'],
- [typeToText(schema), description],
+ [typeToText(type), description],
]);
+
return {content, tableRefs: []};
}
@@ -89,7 +99,7 @@ function prepareObjectSchemaTable(schema: OpenJSONSchema): PrepareObjectSchemaTa
});
if (schema.oneOf?.length) {
- const restElementsDescription = descriptionForOneOfElement(schema);
+ const restElementsDescription = descriptionForOneOfElement(schema, true);
result.rows.push(['...rest', 'oneOf', restElementsDescription]);
}
@@ -101,8 +111,9 @@ type PrepareRowResult = {
type: string;
description: string;
ref?: TableRef;
- /* if object has no ref in RefsService
- * then we will create runtime ref and render it later via AnonymousService
+ /*
+ * if object has no ref in RefsService
+ * then we will create runtime ref and render it later
*/
runtimeRef?: string;
};
@@ -115,12 +126,6 @@ export function prepareTableRowData(
const description = value.description || '';
const propertyRef = parentRef && key && `${parentRef}-${key}`;
- const ref = RefsService.find(value);
-
- if (ref) {
- return {type: anchor(ref), description, ref};
- }
-
const type = inferType(value);
if (type === 'array') {
@@ -133,11 +138,11 @@ export function prepareTableRowData(
? description
: concatNewLine(description, inner.description);
- if (inner.runtimeRef) {
+ if (RefsService.isRuntimeAllowed() && inner.runtimeRef) {
RefsService.runtime(inner.runtimeRef, value.items);
return {
- type: `${anchor(inner.runtimeRef)}[]`,
+ type: `${anchor(inner.runtimeRef, key)}[]`,
runtimeRef: inner.runtimeRef,
description: innerDescription,
};
@@ -151,56 +156,22 @@ export function prepareTableRowData(
};
}
- if (propertyRef && type === 'object') {
+ if (RefsService.isRuntimeAllowed() && propertyRef && type === 'object') {
RefsService.runtime(propertyRef, value);
return {
- type: anchor(propertyRef),
+ type: anchor(propertyRef, key),
runtimeRef: propertyRef,
description: prepareComplexDescription(description, value),
};
}
- if (isComplexType(type)) {
- let runtimeRef: string | undefined;
-
- const {unionOf} = type;
-
- if (unionOf) {
- const unique = unionOf
- .map((el) => {
- if (!isComplexType(el)) {
- return el;
- }
-
- const oneOfRef = el.ref || propertyRef;
-
- if (propertyRef) {
- runtimeRef = propertyRef;
- }
-
- return oneOfRef && anchor(oneOfRef);
- })
- .filter(Boolean)
- .sort((a, b) => a!.length - b!.length);
-
- if (runtimeRef) {
- RefsService.runtime(runtimeRef, value);
- }
-
- return {
- type: unique.join(' or '),
- description,
- runtimeRef,
- };
- }
- }
-
const format = value.format === undefined ? '' : `<${value.format}>`;
return {
- type: typeToText(value) + format,
+ type: typeToText(type) + format,
description: prepareComplexDescription(description, value),
+ ref: extractRefFromType(type),
};
}
@@ -230,19 +201,55 @@ function prepareComplexDescription(baseDescription: string, value: OpenJSONSchem
return description;
}
-// sample key-value JSON body
+function findNonNullOneOfElement(schema: OpenJSONSchema): OpenJSONSchema {
+ const isValid = (v: OpenJSONSchema) => {
+ if (typeof inferType(v) === 'string') {
+ return true;
+ }
+
+ if (Object.keys(v.properties || {}).length) {
+ return true;
+ }
+
+ return false;
+ };
+
+ if (isValid(schema)) {
+ return RefsService.merge(schema);
+ }
+
+ const stack = [...(schema.oneOf || [])];
+
+ while (stack.length) {
+ const v = stack.shift();
+
+ if (!v || typeof v === 'boolean') {
+ continue;
+ }
+
+ if (isValid(v)) {
+ return RefsService.merge(v);
+ }
+
+ stack.push(...(v.oneOf || []));
+ }
+
+ throw new Error(`Unable to create sample element: \n ${stringify(schema, null, 2)}`);
+}
+
export function prepareSampleObject(schema: OpenJSONSchema, callstack: OpenJSONSchema[] = []) {
- const result: {[key: string]: any} = {};
+ const result: {[key: string]: unknown} = {};
if (schema.example) {
return schema.example;
}
- const merged = RefsService.merge(schema);
+ const merged = findNonNullOneOfElement(RefsService.merge(schema));
Object.entries(merged.properties || {}).forEach(([key, value]) => {
const required = isRequired(key, merged);
const possibleValue = prepareSampleElement(key, value, required, callstack);
+
if (possibleValue !== undefined) {
result[key] = possibleValue;
}
@@ -256,25 +263,29 @@ function prepareSampleElement(
v: OpenJSONSchemaDefinition,
required: boolean,
callstack: OpenJSONSchema[],
-): any {
+): unknown {
const value = RefsService.merge(v);
if (value.example) {
return value.example;
}
+
if (value.enum?.length) {
return value.enum[0];
}
+
if (value.default !== undefined) {
return value.default;
}
+
if (!required && callstack.includes(value)) {
// stop recursive cyclic links
return undefined;
}
+
const downCallstack = callstack.concat(value);
const type = inferType(value);
- const schema = value.oneOf?.length ? (value.oneOf[1] as OpenJSONSchema) : value;
+ const schema = findNonNullOneOfElement(value);
switch (type) {
case 'object':
@@ -301,10 +312,12 @@ function prepareSampleElement(
case 'boolean':
return false;
}
+
if (schema.properties) {
// if no "type" specified
return prepareSampleObject(schema, downCallstack);
}
+
return undefined;
}
diff --git a/src/includer/traverse/types.ts b/src/includer/traverse/types.ts
new file mode 100644
index 0000000..635b9a8
--- /dev/null
+++ b/src/includer/traverse/types.ts
@@ -0,0 +1,166 @@
+import RefsService from '../services/refs';
+import stringify from 'json-stringify-safe';
+
+import {EOL, SUPPORTED_ENUM_TYPES} from '../constants';
+
+import {
+ JSONSchemaType,
+ JSONSchemaUnionType,
+ JsType,
+ OpenJSONSchema,
+ SupportedEnumType,
+} from '../models';
+
+import {anchor} from '../ui';
+
+function inferType(value: OpenJSONSchema): JSONSchemaType {
+ if (value === null) {
+ return 'null';
+ }
+
+ const ref = RefsService.find(value);
+
+ if (value.oneOf?.length) {
+ const unionOf = (value.oneOf.filter(Boolean) as OpenJSONSchema[]).map((el) => {
+ const foundRef = RefsService.find(el);
+
+ if (foundRef) {
+ return {ref: foundRef};
+ }
+ const type = inferType(el);
+
+ return type;
+ });
+
+ if (unionOf.length === 1) {
+ return unionOf[0];
+ }
+
+ return {
+ ref,
+ unionOf: [...new Set(unionOf)],
+ };
+ }
+
+ if (ref) {
+ return {ref};
+ }
+
+ if (value.type) {
+ return value.type;
+ }
+
+ if (value.enum) {
+ const enumType = typeof value.enum[0];
+ if (isSupportedEnumType(enumType)) {
+ return enumType;
+ }
+
+ throw new Error(`Unsupported enum type in value: ${stringify(value)}`);
+ }
+
+ if (value.default) {
+ const type = typeof value.default;
+ if (isSupportedEnumType(type)) {
+ return type;
+ }
+ }
+
+ return 'any';
+}
+
+function extractRefFromType(type: JSONSchemaType): string | undefined {
+ if (typeof type === 'string' || isUnionType(type)) {
+ return undefined;
+ }
+
+ if (Array.isArray(type)) {
+ return undefined;
+ }
+
+ return type.ref;
+}
+
+function isUnionType(type: JSONSchemaType): type is JSONSchemaUnionType {
+ if (Array.isArray(type)) {
+ return false;
+ }
+
+ if (typeof type !== 'object') {
+ return false;
+ }
+
+ return 'unionOf' in type && Boolean(type.unionOf?.length);
+}
+
+function typeToText(type: JSONSchemaType): string {
+ if (typeof type === 'string') {
+ return `${type}`;
+ }
+
+ if (Array.isArray(type)) {
+ return 'array';
+ }
+
+ if (isUnionType(type)) {
+ return [...new Set(type.unionOf)].map(typeToText).join(' \nor ');
+ }
+
+ if (type.ref) {
+ return anchor(type.ref);
+ }
+
+ throw new Error(`Unbale to stringify type: ${type}`);
+}
+
+function isSupportedEnumType(enumType: JsType): enumType is SupportedEnumType {
+ return SUPPORTED_ENUM_TYPES.some((type) => enumType === type);
+}
+
+function extractOneOfElements(from: OpenJSONSchema): OpenJSONSchema[] {
+ if (!from.oneOf?.length) {
+ return [];
+ }
+
+ const elements = from.oneOf.filter(Boolean) as OpenJSONSchema[];
+
+ return elements;
+}
+
+function anchorToSchema(item: OpenJSONSchema): string | undefined {
+ const ref = RefsService.find(item);
+
+ return ref ? anchor(ref) : undefined;
+}
+
+function descriptionForOneOfElement(target: OpenJSONSchema, withTypes?: boolean): string {
+ let description = target.description || '';
+
+ const elements = extractOneOfElements(target);
+
+ if (elements.length === 0) {
+ return description;
+ }
+
+ if (withTypes) {
+ if (description.length) {
+ description += EOL;
+ }
+
+ description += extractOneOfElements(target)
+ .map(anchorToSchema)
+ .filter(Boolean)
+ .join(' \nor ');
+ }
+
+ return description;
+}
+
+export {
+ inferType,
+ typeToText,
+ isSupportedEnumType,
+ extractOneOfElements,
+ extractRefFromType,
+ descriptionForOneOfElement,
+};
diff --git a/src/includer/ui/common.ts b/src/includer/ui/common.ts
index d0267b4..6351403 100644
--- a/src/includer/ui/common.ts
+++ b/src/includer/ui/common.ts
@@ -48,7 +48,7 @@ function bold(text: string) {
}
function code(text: string, type = '') {
- const appliedType = type && text.length <= 200 ? type : '';
+ const appliedType = type && text.length <= 2000 ? type : '';
return EOL + ['```' + appliedType, text, '```'].join(EOL) + EOL;
}
@@ -102,8 +102,8 @@ function tabs(tabsObj: Record) {
]);
}
-function anchor(ref: string) {
- return link(ref, `#${slugify(ref).toLowerCase()}`);
+function anchor(ref: string, name?: string) {
+ return link(name || ref, `#${slugify(ref).toLowerCase()}`);
}
function tableParameterName(key: string, required?: boolean) {
diff --git a/src/includer/ui/endpoint.ts b/src/includer/ui/endpoint.ts
index fcb2757..7d27384 100644
--- a/src/includer/ui/endpoint.ts
+++ b/src/includer/ui/endpoint.ts
@@ -1,23 +1,6 @@
import stringify from 'json-stringify-safe';
-import AnonymousService from '../services/anonymous';
import RefsService from '../services/refs';
-import {
- block,
- body,
- bold,
- code,
- cut,
- meta,
- method,
- openapiBlock,
- page,
- table,
- tableParameterName,
- tabs,
- title,
-} from './common';
-
import {
COOKIES_SECTION_NAME,
HEADERS_SECTION_NAME,
@@ -30,6 +13,13 @@ import {
SANDBOX_TAB_NAME,
} from '../constants';
+import {
+ TableRef,
+ prepareSampleObject,
+ prepareTableRowData,
+ tableFromSchema,
+} from '../traverse/tables';
+
import {
Endpoint,
OpenJSONSchema,
@@ -41,14 +31,25 @@ import {
Security,
Server,
} from '../models';
-import {
- TableRef,
- prepareSampleObject,
- prepareTableRowData,
- tableFromSchema,
-} from '../generators/traverse';
+
import {concatNewLine} from '../utils';
+import {
+ block,
+ body,
+ bold,
+ code,
+ cut,
+ meta,
+ method,
+ openapiBlock,
+ page,
+ table,
+ tableParameterName,
+ tabs,
+ title,
+} from './common';
+
function endpoint(data: Endpoint, sandboxPlugin: {host?: string; tabName?: string} | undefined) {
// try to remember, which tables we are already printed on page
const pagePrintedRefs = new Set();
@@ -78,7 +79,6 @@ function endpoint(data: Endpoint, sandboxPlugin: {host?: string; tabName?: strin
parameters(pagePrintedRefs, data.parameters),
openapiBody(pagePrintedRefs, data.requestBody),
responses(pagePrintedRefs, data.responses),
- AnonymousService.render(),
]),
),
]);
@@ -256,12 +256,21 @@ function printAllTables(pagePrintedRefs: Set, tableRefs: TableRef[]): st
}
const schema = RefsService.get(tableRef);
+
+ if (!schema) {
+ continue;
+ }
+
const schemaTable = tableFromSchema(schema);
const titleLevel = schema._runtime ? 4 : 3;
- console.log(tableRef, schema._runtime);
-
- result.push(block([title(titleLevel)(tableRef), schema.description, schemaTable.content]));
+ result.push(
+ block([
+ title(titleLevel)(tableRef),
+ schema._emptyDescription ? '' : schema.description,
+ schemaTable.content,
+ ]),
+ );
tableRefs.push(...schemaTable.tableRefs);
pagePrintedRefs.add(tableRef);
}
diff --git a/src/includer/ui/index.ts b/src/includer/ui/index.ts
index 57da8d8..6d72b22 100644
--- a/src/includer/ui/index.ts
+++ b/src/includer/ui/index.ts
@@ -1,3 +1,9 @@
export * from './common';
-export * from './endpoint';
-export * from './section';
+
+import {endpoint} from './endpoint';
+import {section} from './section';
+import {main} from './main';
+
+export {main, section, endpoint};
+
+export default {main, section, endpoint};
diff --git a/src/includer/generators/main.ts b/src/includer/ui/main.ts
similarity index 95%
rename from src/includer/generators/main.ts
rename to src/includer/ui/main.ts
index 7b7df60..0223f0b 100644
--- a/src/includer/generators/main.ts
+++ b/src/includer/ui/main.ts
@@ -2,7 +2,8 @@ import stringify from 'json-stringify-safe';
import {sep} from 'path';
-import {block, body, code, cut, link, list, mono, page, title} from '../ui';
+import {block, body, code, cut, link, list, mono, page, title} from '.';
+
import {
CONTACTS_SECTION_NAME,
ENDPOINTS_SECTION_NAME,
@@ -20,10 +21,11 @@ import {
Specification,
Tag,
} from '../models';
+
import {mdPath, sectionName} from '../index';
export type MainParams = {
- data: any;
+ data: unknown;
info: Info;
spec: Specification;
leadingPageSpecRenderMode: LeadingPageSpecRenderMode;
@@ -85,7 +87,7 @@ function sections({tags, endpoints}: Specification) {
return content.length && block(content);
}
-function specification(data: any, renderMode: LeadingPageSpecRenderMode) {
+function specification(data: unknown, renderMode: LeadingPageSpecRenderMode) {
return (
renderMode === SPEC_RENDER_MODE_DEFAULT &&
block([title(2)(SPEC_SECTION_NAME), cut(code(stringify(data, null, 4)), SPEC_SECTION_TYPE)])
diff --git a/src/includer/utils.ts b/src/includer/utils.ts
index 2c3f996..185f9a2 100644
--- a/src/includer/utils.ts
+++ b/src/includer/utils.ts
@@ -1,8 +1,5 @@
-import RefsService from './services/refs';
-
-import {Endpoint, OpenApiIncluderParams, OpenJSONSchema, Specification, Tag} from './models';
+import {Endpoint, OpenApiIncluderParams, Specification, Tag} from './models';
import {evalExp} from '@diplodoc/transform/lib/liquid/evaluation';
-import {anchor} from './ui';
export function concatNewLine(prefix: string, suffix: string) {
return prefix.trim().length ? `${prefix}
${suffix}` : suffix;
@@ -48,33 +45,3 @@ export function matchFilter(
}
};
}
-
-export function extractOneOfElements(from: OpenJSONSchema): OpenJSONSchema[] {
- if (!from.oneOf?.length) {
- return [];
- }
-
- const elements = from.oneOf.filter(Boolean) as OpenJSONSchema[];
-
- return elements;
-}
-
-export function descriptionForOneOfElement(target: OpenJSONSchema): string {
- const elements = extractOneOfElements(target);
-
- if (elements.length === 0) {
- return '';
- }
-
- const description = elements
- .map((item) => createOneOfDescription(item))
- .filter(Boolean)
- .join('\nor ');
-
- return description;
-}
-
-export function createOneOfDescription(item: OpenJSONSchema): string | undefined {
- const ref = RefsService.find(item);
- return ref ? anchor(ref) : item.description;
-}