Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normalize security definitions differently depending on the spec version #58

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/includer/normalizer/normalize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* eslint-disable camelcase */
import {OpenAPI, OpenAPIV2, OpenAPIV3, OpenAPIV3_1} from 'openapi-types';
import {ISpecNormalizationStrategies, NormalizedSecurityDefinitions} from './strategies/defs';
import {OASVersionGeneration, resolveOASVersionGeneration} from './resolveOASGeneration';
import {oasV2NormalizationStrategies} from './strategies/2.0';
import {oasV3NormalizationStrategies} from './strategies/3.0';
import {oasV31NormalizationStrategies} from './strategies/3.1';

export type NormalizedSpec = {
securityDefinitions: NormalizedSecurityDefinitions;
};

type GenerationToDocumentType = {
[OASVersionGeneration.V2_0]: OpenAPIV2.Document;
[OASVersionGeneration.V3_0]: OpenAPIV3.Document;
[OASVersionGeneration.V3_1]: OpenAPIV3_1.Document;
};

type GenerationToStrategyMapping = {
[Gen in OASVersionGeneration]: ISpecNormalizationStrategies<GenerationToDocumentType[Gen]>;
};

const generationToStrategyMap: GenerationToStrategyMapping = {
[OASVersionGeneration.V2_0]: oasV2NormalizationStrategies,
[OASVersionGeneration.V3_0]: oasV3NormalizationStrategies,
[OASVersionGeneration.V3_1]: oasV31NormalizationStrategies,
};

// This is a stub for now
// This should probably consist of all the custom schema types defined in `models.ts`
// The idea is to normalize `OpenAPI.Document` so that it provides a consistent
// way of rendering stuff we actually care about, regardless of the spec version
export const normalize = (spec: OpenAPI.Document): NormalizedSpec => {
const specGeneration = resolveOASVersionGeneration(spec);
const strategies = generationToStrategyMap[specGeneration] as ISpecNormalizationStrategies;

return {
securityDefinitions: strategies.normalizeSecurityDefinitions(spec),
};
};
32 changes: 32 additions & 0 deletions src/includer/normalizer/resolveOASGeneration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {OpenAPI, OpenAPIV2} from 'openapi-types';

export enum OASVersionGeneration {
V2_0,
V3_0,
V3_1,
}

// this is probably a bit verbose, but `swagger-parser` we use has a similar bit of code
// https://github.com/APIDevTools/swagger-parser/blob/1d9776e2445c3dfc62cf2cd63a33f3449e5ed9fa/lib/index.js#L11
const versionMap = {
'2.0': OASVersionGeneration.V2_0,
'3.0.0': OASVersionGeneration.V3_0,
'3.0.1': OASVersionGeneration.V3_0,
'3.0.2': OASVersionGeneration.V3_0,
'3.0.3': OASVersionGeneration.V3_0,
'3.1.0': OASVersionGeneration.V3_1,
} satisfies Record<string, OASVersionGeneration>;

const isLegacySwaggerSpec = (spec: OpenAPI.Document): spec is OpenAPIV2.Document =>
Object.prototype.hasOwnProperty.call(spec, 'swagger');

export const resolveOASVersionGeneration = (spec: OpenAPI.Document): OASVersionGeneration => {
const resolvedVersion = isLegacySwaggerSpec(spec) ? spec.swagger : spec.openapi;

if (resolvedVersion in versionMap) {
return versionMap[resolvedVersion as keyof typeof versionMap];
}

// technically, this throw is pointless, since `swagger-parser` already should have this checked beforehand
throw new TypeError(`Unsupported spec version: ${resolvedVersion}`);
};
9 changes: 9 additions & 0 deletions src/includer/normalizer/shared/isOASV3XReferenceObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* eslint-disable camelcase */
import {OpenAPIV3, OpenAPIV3_1} from 'openapi-types';

type OASV3XReferenceObject = OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject;

export const isOASV3XReferenceObject = (
maybeReferenceObject: object,
): maybeReferenceObject is OASV3XReferenceObject =>
Object.prototype.hasOwnProperty.call(maybeReferenceObject, '$ref');
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* eslint-disable camelcase */
import {OpenAPIV3, OpenAPIV3_1} from 'openapi-types';
import {isOASV3XReferenceObject} from './isOASV3XReferenceObject';
import {Security} from '../../models';

type OASV3XSpec = OpenAPIV3.Document | OpenAPIV3_1.Document;
type OASV3XSecurityScheme = OpenAPIV3.SecuritySchemeObject | OpenAPIV3_1.SecuritySchemeObject;

const definitionIsSecurityScheme = (
objectEntry: [string, object],
): objectEntry is [string, OASV3XSecurityScheme] => {
const [, maybeScheme] = objectEntry;

return !isOASV3XReferenceObject(maybeScheme);
};

export const normalizeOASV3XSecurityDefinitions = (spec: OASV3XSpec) =>
Object.fromEntries(
Object.entries(spec.components?.securitySchemes ?? {})
.filter(definitionIsSecurityScheme)
.map(([schemeName, {type, description}]) => {
const normalizedScheme: Security = {
type,
description: description ?? '',
};

return [schemeName, normalizedScheme];
}),
);
19 changes: 19 additions & 0 deletions src/includer/normalizer/strategies/2.0/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {OpenAPIV2} from 'openapi-types';
import {ISpecNormalizationStrategies} from '../defs';
import {Security} from '../../../models';

export const oasV2NormalizationStrategies: ISpecNormalizationStrategies<OpenAPIV2.Document> = {
normalizeSecurityDefinitions: (spec) =>
Object.fromEntries(
Object.entries(spec.securityDefinitions ?? {}).map(
([schemeName, {type, description}]) => {
const normalizedScheme: Security = {
type,
description: description ?? '',
};

return [schemeName, normalizedScheme];
},
),
),
};
7 changes: 7 additions & 0 deletions src/includer/normalizer/strategies/3.0/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {OpenAPIV3} from 'openapi-types';
import {ISpecNormalizationStrategies} from '../defs';
import {normalizeOASV3XSecurityDefinitions} from '../../shared/normalizeOASV3XSecurityDefinitions';

export const oasV3NormalizationStrategies: ISpecNormalizationStrategies<OpenAPIV3.Document> = {
normalizeSecurityDefinitions: normalizeOASV3XSecurityDefinitions,
};
8 changes: 8 additions & 0 deletions src/includer/normalizer/strategies/3.1/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* eslint-disable camelcase */
import {OpenAPIV3_1} from 'openapi-types';
import {ISpecNormalizationStrategies} from '../defs';
import {normalizeOASV3XSecurityDefinitions} from '../../shared/normalizeOASV3XSecurityDefinitions';

export const oasV31NormalizationStrategies: ISpecNormalizationStrategies<OpenAPIV3_1.Document> = {
normalizeSecurityDefinitions: normalizeOASV3XSecurityDefinitions,
};
9 changes: 9 additions & 0 deletions src/includer/normalizer/strategies/defs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {OpenAPI} from 'openapi-types';
import {Security} from '../../models';

export type NormalizedSecurityDefinitions = Record<string, Security>;

export type ISpecNormalizationStrategies<ConcreteSpec extends OpenAPI.Document = OpenAPI.Document> =
{
normalizeSecurityDefinitions: (spec: ConcreteSpec) => NormalizedSecurityDefinitions;
};
21 changes: 10 additions & 11 deletions src/includer/parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
Specification,
Tag,
} from './models';
import {normalize} from './normalizer/normalize';
import {OpenAPI} from 'openapi-types';

function info(spec: OpenAPISpec): Info {
const {
Expand Down Expand Up @@ -115,8 +117,12 @@ function tagsFromSpec(spec: OpenAPISpec): Map<string, Tag> {
const opid = (path: string, method: string, id?: string) => slugify(id ?? [path, method].join('-'));

function pathsFromSpec(spec: OpenAPISpec, tagsByID: Map<string, Tag>): Specification {
// This conversion (OAPI.Doc -> OAS (with `any`) -> OAPI.Doc) is crude, but this whole file consists of crude code, so...
// (someday this will be a part of normalization routine I guess)
const prenormalizedSpec = normalize(spec as OpenAPI.Document);

const endpoints: Endpoints = [];
const {paths, servers, components = {}, security: globalSecurity = []} = spec;
const {paths, servers, security: globalSecurity = []} = spec;
const visiter = ({path, method, endpoint}: VisiterParams) => {
const {
summary,
Expand All @@ -129,16 +135,9 @@ function pathsFromSpec(spec: OpenAPISpec, tagsByID: Map<string, Tag>): Specifica
security = [],
} = endpoint;

const parsedSecurity = [...security, ...globalSecurity].reduce((arr, item) => {
arr.push(
...Object.keys(item).reduce((acc, key) => {
// @ts-ignore
acc.push(components.securitySchemes[key]);
return acc;
}, []),
);
return arr;
}, []);
const parsedSecurity = [...security, ...globalSecurity].flatMap((item) =>
Object.keys(item).map((key) => prenormalizedSpec.securityDefinitions[key]),
);

const parsedServers = (endpoint.servers || servers || [{url: '/'}]).map(
(server: Server) => {
Expand Down
Loading