diff --git a/src/components/Schema/__tests__/DiscriminatorDropdown.test.tsx b/src/components/Schema/__tests__/DiscriminatorDropdown.test.tsx index 609403ec..40757339 100644 --- a/src/components/Schema/__tests__/DiscriminatorDropdown.test.tsx +++ b/src/components/Schema/__tests__/DiscriminatorDropdown.test.tsx @@ -8,30 +8,31 @@ import { Schema } from '../Schema'; import { ObjectSchema } from '../ObjectSchema'; import * as simpleDiscriminatorFixture from './fixtures/simple-discriminator.json'; +const options = new RedocNormalizedOptions({}); describe('Components', () => { describe('SchemaView', () => { describe('discriminator', () => { it('should correctly render SchemaView', () => { - const parser = new OpenAPIParser(simpleDiscriminatorFixture); + const parser = new OpenAPIParser(simpleDiscriminatorFixture, undefined, options); const schema = new SchemaModel( parser, { $ref: '#/components/schemas/Pet' }, '#/components/schemas/Pet', - new RedocNormalizedOptions({}), + options, ); const schemaView = shallow(); expect(toJson(schemaView)).toMatchSnapshot(); }); it('should correctly render discriminator dropdown', () => { - const parser = new OpenAPIParser(simpleDiscriminatorFixture); + const parser = new OpenAPIParser(simpleDiscriminatorFixture, undefined, options); const schema = new SchemaModel( parser, { $ref: '#/components/schemas/Pet' }, '#/components/schemas/Pet', - new RedocNormalizedOptions({}), + options, ); const schemaView = shallow( -->`; +} type MarkdownHeading = { name: string; diff --git a/src/services/OpenAPIParser.ts b/src/services/OpenAPIParser.ts index 34beccb7..98d8bca0 100644 --- a/src/services/OpenAPIParser.ts +++ b/src/services/OpenAPIParser.ts @@ -5,6 +5,9 @@ import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from '../types'; import { JsonPointer } from '../utils/JsonPointer'; import { isNamedDefinition } from '../utils/openapi'; +import { COMPONENT_REGEXP, buildComponentComment } from './MarkdownRenderer'; +import { RedocNormalizedOptions } from './RedocNormalizedOptions'; +import { appendToMdHeading } from '../utils/index'; export type MergedOpenAPISchema = OpenAPISchema & { namedParents?: string[] }; @@ -39,8 +42,13 @@ export class OpenAPIParser { @observable specUrl: string; @observable.ref spec: OpenAPISpec; - constructor(spec: OpenAPISpec, specUrl?: string) { + constructor( + spec: OpenAPISpec, + specUrl: string | undefined, + private options: RedocNormalizedOptions, + ) { this.validate(spec); + this.preprocess(spec); this.spec = spec; @@ -59,6 +67,22 @@ export class OpenAPIParser { } } + preprocess(spec: OpenAPISpec) { + if (!this.options.noAutoAuth && spec.info) { + // Automatically inject Authentication section with SecurityDefinitions component + const description = spec.info.description || ''; + const securityRegexp = new RegExp( + COMPONENT_REGEXP.replace('{component}', ''), + 'gmi', + ); + debugger; + if (!securityRegexp.test(description)) { + const comment = buildComponentComment('security-definitions'); + spec.info.description = appendToMdHeading(description, 'Authentication', comment); + } + } + } + /** * get spec part by JsonPointer ($ref) */ diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index 4a1b98c8..8bce7980 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -8,6 +8,7 @@ export interface RedocRawOptions { hideHostname?: boolean | string; expandResponses?: string | 'all'; requiredPropsFirst?: boolean | string; + noAutoAuth?: boolean | string; } function argValueToBoolean(val?: string | boolean): boolean { @@ -22,6 +23,7 @@ export class RedocNormalizedOptions { hideHostname: boolean; expandResponses: { [code: string]: boolean } | 'all'; requiredPropsFirst: boolean; + noAutoAuth: boolean; constructor(raw: RedocRawOptions) { this.theme = { ...(raw.theme || {}), ...defaultTheme }; // todo: merge deep @@ -29,6 +31,7 @@ export class RedocNormalizedOptions { this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname); this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses); this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst); + this.noAutoAuth = argValueToBoolean(raw.noAutoAuth); } static normalizeExpandResponses(value: RedocRawOptions['expandResponses']) { diff --git a/src/services/SpecStore.ts b/src/services/SpecStore.ts index 69a8ee22..33fbdd95 100644 --- a/src/services/SpecStore.ts +++ b/src/services/SpecStore.ts @@ -19,7 +19,7 @@ export class SpecStore { specUrl: string | undefined, private options: RedocNormalizedOptions, ) { - this.parser = new OpenAPIParser(spec, specUrl); + this.parser = new OpenAPIParser(spec, specUrl, options); } @computed diff --git a/src/services/__tests__/models/Response.test.ts b/src/services/__tests__/models/Response.test.ts index 5f433012..77b861bb 100644 --- a/src/services/__tests__/models/Response.test.ts +++ b/src/services/__tests__/models/Response.test.ts @@ -8,7 +8,7 @@ describe('Models', () => { let parser; beforeEach(() => { - parser = new OpenAPIParser({ openapi: '3.0.0' } as any); + parser = new OpenAPIParser({ openapi: '3.0.0' } as any, undefined, opts); }); test('should calculate response type based on code', () => { diff --git a/src/utils/__tests__/helpers.test.ts b/src/utils/__tests__/helpers.test.ts index 0d3e3947..8a18b84a 100644 --- a/src/utils/__tests__/helpers.test.ts +++ b/src/utils/__tests__/helpers.test.ts @@ -1,4 +1,4 @@ -import { mapWithLast } from '../helpers'; +import { mapWithLast, appendToMdHeading } from '../helpers'; describe('Utils', () => { describe('helpers', () => { @@ -19,5 +19,24 @@ describe('Utils', () => { const expected = []; expect(actual).toEqual(expected); }); + + test('appendToMdHeading heading exists not last', () => { + const val = appendToMdHeading( + '# Authentication\n Hello\n# Next heading', + 'Authentication', + '', + ); + expect(val).toEqual('# Authentication\n Hello\n\n\n\n# Next heading'); + }); + + test('appendToMdHeading heading exists last', () => { + const val = appendToMdHeading('# Authentication\n Hello', 'Authentication', ''); + expect(val).toEqual('# Authentication\n Hello\n\n\n'); + }); + + test('appendToMdHeading empty string', () => { + const val = appendToMdHeading('', 'Authentication', ''); + expect(val).toEqual('# Authentication\n\n'); + }); }); }); diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index ccd39562..8452669f 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -67,3 +67,16 @@ export function isAbsolutePath(path: string): boolean { export function isNumeric(n: any): n is Number { return !isNaN(parseFloat(n)) && isFinite(n); } + +export function appendToMdHeading(md: string, heading: string, content: string) { + // if heading is already in md and append to the end of it + const testRegex = new RegExp(`(^|\\n)#\\s?${heading}\\s*\\n`, 'i'); + const replaceRegex = new RegExp(`((\\n|^)#\\s*${heading}\\s*(\\n|$)(?:.|\\n)*?)(\\n#|$)`, 'i'); + if (testRegex.test(md)) { + return md.replace(replaceRegex, `$1\n\n${content}\n$4`); + } else { + // else append heading itself + const br = md === '' || md.endsWith('\n\n') ? '' : md.endsWith('\n') ? '\n' : '\n\n'; + return `${md}${br}# ${heading}\n\n${content}`; + } +}