Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .changeset/orange-squids-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@redocly/openapi-core": patch
"@redocly/cli": patch
---

Improved Redocly config validation: now the config checks for typos in built-in decorator names.
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,18 @@ exports[`createConfigTypes > matches snapshot for the default config schema 1`]
},
},
},
"BuiltinDecorator": {
"additionalProperties": {},
"description": "Built-in decorators are the default decorators included with Redocly CLI.",
"documentationLink": "https://redocly.com/docs/cli/decorators#list-of-decorators",
"properties": {},
},
"BuiltinPreprocessor": {
"additionalProperties": {},
"description": "Built-in preprocessors are the default preprocessors included with Redocly CLI. The available options are the same for decorators and preprocessors.",
"documentationLink": "https://redocly.com/docs/cli/decorators#list-of-decorators",
"properties": {},
},
"BuiltinRule": {
"additionalProperties": {},
"description": "Built-in rules are the default linting rules included with Redocly CLI. They enforce widely accepted API design standards and best practices. Each built-in rule has a default severity of error, but you can change the severity or turn off any built-in rule in your configuration file.",
Expand Down Expand Up @@ -516,8 +528,37 @@ exports[`createConfigTypes > matches snapshot for the default config schema 1`]
"assertions",
],
},
"Decorators": {
"CustomDecorator": {
"additionalProperties": {},
"description": "Custom decorators are defined by users via plugins.",
"documentationLink": "https://redocly.com/docs/cli/custom-plugins/custom-decorators",
"properties": {},
},
"CustomPreprocessor": {
"additionalProperties": {},
"description": "Custom preprocessors are defined by users via plugins. The available options are the same for decorators and preprocessors.",
"documentationLink": "https://redocly.com/docs/cli/custom-plugins/custom-decorators",
"properties": {},
},
"CustomRule": {
"additionalProperties": {},
"description": "Custom rules are defined by users via plugins.",
"documentationLink": "https://redocly.com/docs/cli/custom-plugins/custom-rules",
"properties": {
"severity": {
"enum": [
"error",
"warn",
"off",
],
},
},
"required": [
"severity",
],
},
"Decorators": {
"additionalProperties": [Function],
"description": "Decorators define transformations that can be applied to the API document during the bundle step.",
"documentationLink": "https://redocly.com/docs/cli/configuration/reference/decorators",
"properties": {},
Expand Down Expand Up @@ -723,7 +764,7 @@ exports[`createConfigTypes > matches snapshot for the default config schema 1`]
"required": undefined,
},
"Preprocessors": {
"additionalProperties": {},
"additionalProperties": [Function],
"description": "Preprocessors define transformations that can be applied to the API document before bundling.",
"documentationLink": "https://redocly.com/docs/cli/configuration/reference/preprocessors",
"properties": {},
Expand Down
51 changes: 50 additions & 1 deletion packages/core/src/__tests__/redocly-yaml.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as redoclyConfig from '@redocly/config';

import { createConfigTypes } from '../types/redocly-yaml.js';
import type { ResolveTypeFn } from '../types/index.js';
import { ConfigTypes, createConfigTypes } from '../types/redocly-yaml.js';

describe('createConfigTypes', () => {
it('matches snapshot for the default config schema', () => {
Expand All @@ -15,3 +16,51 @@ describe('createConfigTypes', () => {
).toMatchSnapshot();
});
});

describe('Rules NodeType resolving', () => {
const rulesResolver = ConfigTypes.Rules.additionalProperties as ResolveTypeFn;

it('resolves built-in rule names', () => {
expect(rulesResolver({}, 'no-unresolved-refs')).toBe('BuiltinRule');
});

it('resolves custom (plugin-prefixed) rule names', () => {
expect(rulesResolver({}, 'my-plugin/my-custom-rule')).toBe('CustomRule');
});

it('resolves configurable rule names (rule/ prefix)', () => {
expect(rulesResolver({}, 'rule/my-configurable-rule')).toBe('ConfigurableRule');
});

it('resolves schema-related keys', () => {
expect(rulesResolver({}, 'metadata-schema')).toBe('Schema');
expect(rulesResolver({}, 'custom-fields-schema')).toBe('Schema');
});

it('returns undefined for unknown rule names', () => {
expect(rulesResolver({}, 'not-a-real-rule')).toBeUndefined();
});
});

describe('Decorators / Preprocessors NodeType resolving', () => {
const decoratorsResolver = ConfigTypes.Decorators.additionalProperties as ResolveTypeFn;
const preprocessorsResolver = ConfigTypes.Preprocessors.additionalProperties as ResolveTypeFn;

it('resolves built-in decorator names', () => {
expect(decoratorsResolver({}, 'remove-unused-components')).toBe('BuiltinDecorator');
});

it('resolves custom (plugin-prefixed) decorator names', () => {
expect(decoratorsResolver({}, 'my-plugin/my-custom-decorator')).toBe('CustomDecorator');
});

it('returns undefined for unknown decorator names', () => {
expect(decoratorsResolver({}, 'not-a-real-decorator')).toBeUndefined();
});

it('resolves built-in and custom preprocessor names', () => {
expect(preprocessorsResolver({}, 'remove-unused-components')).toBe('BuiltinPreprocessor');
expect(preprocessorsResolver({}, 'my-plugin/my-preprocessor')).toBe('CustomPreprocessor');
expect(preprocessorsResolver({}, 'unknown')).toBeUndefined();
});
});
62 changes: 39 additions & 23 deletions packages/core/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,22 @@ import type { ApiConfig, RedoclyConfig } from '@redocly/config';
import type { JSONSchema } from 'json-schema-to-ts';

import type {
Oas3PreprocessorsSet,
SpecMajorVersion,
Oas3DecoratorsSet,
Oas2RuleSet,
Oas2PreprocessorsSet,
Oas2DecoratorsSet,
Oas3RuleSet,
SpecVersion,
Async2PreprocessorsSet,
Async2DecoratorsSet,
Async2RuleSet,
Async3PreprocessorsSet,
Async3DecoratorsSet,
Async3RuleSet,
Arazzo1RuleSet,
Arazzo1PreprocessorsSet,
Arazzo1DecoratorsSet,
RuleMap,
Overlay1PreprocessorsSet,
Overlay1DecoratorsSet,
Overlay1RuleSet,
OpenRpc1RuleSet,
OpenRpc1PreprocessorsSet,
OpenRpc1DecoratorsSet,
} from '../oas-types.js';
import type { Location } from '../ref-utils.js';
Expand All @@ -38,6 +31,8 @@ import type {
BuiltInOverlay1RuleId,
BuiltInOpenRpc1RuleId,
BuiltInCommonRuleId,
BuiltInOas2DecoratorId,
BuiltInOas3DecoratorId,
} from '../types/redocly-yaml.js';
import type { SkipFunctionContext } from '../visitors.js';
import type { ProblemSeverity, UserContext } from '../walk.js';
Expand Down Expand Up @@ -99,22 +94,43 @@ export type RawGovernanceConfig<T extends 'built-in' | undefined = undefined> =
overlay1Rules?: RuleMap<BuiltInOverlay1RuleId, RuleConfig, T>;
openrpc1Rules?: RuleMap<BuiltInOpenRpc1RuleId, RuleConfig, T>;

preprocessors?: Record<string, PreprocessorConfig>;
oas2Preprocessors?: Record<string, PreprocessorConfig>;
oas3_0Preprocessors?: Record<string, PreprocessorConfig>;
oas3_1Preprocessors?: Record<string, PreprocessorConfig>;
oas3_2Preprocessors?: Record<string, PreprocessorConfig>;
preprocessors?: Record<string, DecoratorConfig>;
oas2Preprocessors?: Record<
T extends 'built-in' ? BuiltInOas2DecoratorId : string,
DecoratorConfig
>;
oas3_0Preprocessors?: Record<
T extends 'built-in' ? BuiltInOas3DecoratorId : string,
DecoratorConfig
>;
oas3_1Preprocessors?: Record<
T extends 'built-in' ? BuiltInOas3DecoratorId : string,
DecoratorConfig
>;
oas3_2Preprocessors?: Record<
T extends 'built-in' ? BuiltInOas3DecoratorId : string,
DecoratorConfig
>;
async2Preprocessors?: Record<string, PreprocessorConfig>;
async3Preprocessors?: Record<string, PreprocessorConfig>;
arazzo1Preprocessors?: Record<string, PreprocessorConfig>;
overlay1Preprocessors?: Record<string, PreprocessorConfig>;
openrpc1Preprocessors?: Record<string, PreprocessorConfig>;

decorators?: Record<string, DecoratorConfig>;
oas2Decorators?: Record<string, DecoratorConfig>;
oas3_0Decorators?: Record<string, DecoratorConfig>;
oas3_1Decorators?: Record<string, DecoratorConfig>;
oas3_2Decorators?: Record<string, DecoratorConfig>;
oas2Decorators?: Record<T extends 'built-in' ? BuiltInOas2DecoratorId : string, DecoratorConfig>;
oas3_0Decorators?: Record<
T extends 'built-in' ? BuiltInOas3DecoratorId : string,
DecoratorConfig
>;
oas3_1Decorators?: Record<
T extends 'built-in' ? BuiltInOas3DecoratorId : string,
DecoratorConfig
>;
oas3_2Decorators?: Record<
T extends 'built-in' ? BuiltInOas3DecoratorId : string,
DecoratorConfig
>;
async2Decorators?: Record<string, DecoratorConfig>;
async3Decorators?: Record<string, DecoratorConfig>;
arazzo1Decorators?: Record<string, DecoratorConfig>;
Expand All @@ -125,13 +141,13 @@ export type RawGovernanceConfig<T extends 'built-in' | undefined = undefined> =
export type ResolvedGovernanceConfig = Omit<RawGovernanceConfig, 'extends' | 'plugins'>;

export type PreprocessorsConfig = {
oas3?: Oas3PreprocessorsSet;
oas2?: Oas2PreprocessorsSet;
async2?: Async2PreprocessorsSet;
async3?: Async3PreprocessorsSet;
arazzo1?: Arazzo1PreprocessorsSet;
overlay1?: Overlay1PreprocessorsSet;
openrpc1?: OpenRpc1PreprocessorsSet;
oas3?: Oas3DecoratorsSet;
oas2?: Oas2DecoratorsSet;
async2?: Async2DecoratorsSet;
async3?: Async3DecoratorsSet;
arazzo1?: Arazzo1DecoratorsSet;
overlay1?: Overlay1DecoratorsSet;
openrpc1?: OpenRpc1DecoratorsSet;
};

export type DecoratorsConfig = {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/decorators/oas2/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { type Oas2DecoratorsSet } from '../../oas-types.js';
import type { Oas2Decorator } from '../../visitors.js';
import { FilterIn } from '../common/filters/filter-in.js';
import { FilterOut } from '../common/filters/filter-out.js';
Expand All @@ -8,7 +9,7 @@ import { RemoveXInternal } from '../common/remove-x-internal.js';
import { TagDescriptionOverride } from '../common/tag-description-override.js';
import { RemoveUnusedComponents } from './remove-unused-components.js';

export const decorators = {
export const decorators: Oas2DecoratorsSet<'built-in'> = {
'operation-description-override': OperationDescriptionOverride as Oas2Decorator,
'tag-description-override': TagDescriptionOverride as Oas2Decorator,
'info-description-override': InfoDescriptionOverride as Oas2Decorator,
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/decorators/oas3/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
// FIXME: rename common to common-oas
import type { Oas3Decorator } from '../../visitors.js';
import { type Oas3DecoratorsSet } from '../../oas-types.js';
import { type Oas3Decorator } from '../../visitors.js';
import { FilterIn } from '../common/filters/filter-in.js';
import { FilterOut } from '../common/filters/filter-out.js';
import { InfoDescriptionOverride } from '../common/info-description-override.js';
import { InfoOverride } from '../common/info-override.js';
import { MediaTypeExamplesOverride } from '../common/media-type-examples-override.js';
import { OperationDescriptionOverride } from '../common/operation-description-override.js';
import { RemoveXInternal } from '../common/remove-x-internal.js';
import { TagDescriptionOverride } from '../common/tag-description-override.js';
import { MediaTypeExamplesOverride } from './media-type-examples-override.js';
import { RemoveUnusedComponents } from './remove-unused-components.js';

export const decorators = {
export const decorators: Oas3DecoratorsSet<'built-in'> = {
'operation-description-override': OperationDescriptionOverride as Oas3Decorator,
'tag-description-override': TagDescriptionOverride as Oas3Decorator,
'info-description-override': InfoDescriptionOverride as Oas3Decorator,
'info-override': InfoOverride as Oas3Decorator,
'remove-x-internal': RemoveXInternal as Oas3Decorator,
'filter-in': FilterIn as Oas3Decorator,
'filter-out': FilterOut as Oas3Decorator,
'media-type-examples-override': MediaTypeExamplesOverride as Oas3Decorator,
'media-type-examples-override': MediaTypeExamplesOverride,
'remove-unused-components': RemoveUnusedComponents,
};
14 changes: 7 additions & 7 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ export {
type Arazzo1Decorator,
type Overlay1Decorator,
type OpenRpc1Decorator,
type Oas3Preprocessor,
type Oas2Preprocessor,
type Async2Preprocessor,
type Async3Preprocessor,
type Arazzo1Preprocessor,
type Overlay1Preprocessor,
type OpenRpc1Preprocessor,
type Oas3Decorator as Oas3Preprocessor,
type Oas2Decorator as Oas2Preprocessor,
type Async2Decorator as Async2Preprocessor,
type Async3Decorator as Async3Preprocessor,
type Arazzo1Decorator as Arazzo1Preprocessor,
type Overlay1Decorator as Overlay1Preprocessor,
type OpenRpc1Decorator as OpenRpc1Preprocessor,
} from './visitors.js';
export {
WalkContext,
Expand Down
44 changes: 22 additions & 22 deletions packages/core/src/oas-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,23 @@ import type {
BuiltInOverlay1RuleId,
BuiltInCommonRuleId,
BuiltInOpenRpc1RuleId,
BuiltInOas2DecoratorId,
BuiltInOas3DecoratorId,
} from './types/redocly-yaml.js';
import type {
Oas3Rule,
Oas3Preprocessor,
Oas3Decorator,
Oas2Rule,
Oas2Preprocessor,
Async2Preprocessor,
Oas2Decorator,
Async2Decorator,
Async2Rule,
Async3Preprocessor,
Async3Decorator,
Async3Rule,
Arazzo1Preprocessor,
Arazzo1Decorator,
Arazzo1Rule,
Overlay1Preprocessor,
Overlay1Decorator,
Overlay1Rule,
OpenRpc1Preprocessor,
OpenRpc1Decorator,
OpenRpc1Rule,
} from './visitors.js';

Expand Down Expand Up @@ -110,21 +112,19 @@ export type OpenRpc1RuleSet<T = undefined> = RuleMap<
T
>;

export type Oas3PreprocessorsSet = Record<string, Oas3Preprocessor>;
export type Oas2PreprocessorsSet = Record<string, Oas2Preprocessor>;
export type Async2PreprocessorsSet = Record<string, Async2Preprocessor>;
export type Async3PreprocessorsSet = Record<string, Async3Preprocessor>;
export type Arazzo1PreprocessorsSet = Record<string, Arazzo1Preprocessor>;
export type Overlay1PreprocessorsSet = Record<string, Overlay1Preprocessor>;
export type OpenRpc1PreprocessorsSet = Record<string, OpenRpc1Preprocessor>;

export type Oas3DecoratorsSet = Record<string, Oas3Preprocessor>;
export type Oas2DecoratorsSet = Record<string, Oas2Preprocessor>;
export type Async2DecoratorsSet = Record<string, Async2Preprocessor>;
export type Async3DecoratorsSet = Record<string, Async3Preprocessor>;
export type Arazzo1DecoratorsSet = Record<string, Arazzo1Preprocessor>;
export type Overlay1DecoratorsSet = Record<string, Overlay1Preprocessor>;
export type OpenRpc1DecoratorsSet = Record<string, OpenRpc1Preprocessor>;
export type Oas3DecoratorsSet<T = undefined> = Record<
T extends 'built-in' ? BuiltInOas3DecoratorId : string,
Oas3Decorator
>;
export type Oas2DecoratorsSet<T = undefined> = Record<
T extends 'built-in' ? BuiltInOas2DecoratorId : string,
Oas2Decorator
>;
export type Async2DecoratorsSet = Record<string, Async2Decorator>;
export type Async3DecoratorsSet = Record<string, Async3Decorator>;
export type Arazzo1DecoratorsSet = Record<string, Arazzo1Decorator>;
export type Overlay1DecoratorsSet = Record<string, Overlay1Decorator>;
export type OpenRpc1DecoratorsSet = Record<string, OpenRpc1Decorator>;

export function getTypes(spec: SpecVersion) {
return typesMap[spec];
Expand Down
Loading
Loading