diff --git a/.changeset/funny-lobsters-thank.md b/.changeset/funny-lobsters-thank.md new file mode 100644 index 0000000000..829bb80de0 --- /dev/null +++ b/.changeset/funny-lobsters-thank.md @@ -0,0 +1,6 @@ +--- +"@redocly/openapi-core": minor +"@redocly/cli": minor +--- + +Added support for linting, preprocessors, decorators, and type extensions for Overlay v1 documents. diff --git a/__tests__/check-config/wrong-config-type-extensions-in-assertions/snapshot.js b/__tests__/check-config/wrong-config-type-extensions-in-assertions/snapshot.js index 3798896963..fbfc95c5ee 100644 --- a/__tests__/check-config/wrong-config-type-extensions-in-assertions/snapshot.js +++ b/__tests__/check-config/wrong-config-type-extensions-in-assertions/snapshot.js @@ -5,7 +5,7 @@ exports[`E2E check-config wrong config type extension in assertions 1`] = ` Deprecated plugin format detected: type-extension [1] redocly.yaml:10:13 at #/rules/rule~1metadata-lifecycle/subject/type -\`type\` can be one of the following only: "any", "Root", "Tag", "TagList", "TagGroups", "TagGroup", "ExternalDocs", "Example", "ExamplesMap", "EnumDescriptions", "SecurityRequirement", "SecurityRequirementList", "Info", "Contact", "License", "Logo", "Paths", "PathItem", "Parameter", "ParameterItems", "ParameterList", "Operation", "Examples", "Header", "Responses", "Response", "Schema", "Xml", "SchemaProperties", "NamedSchemas", "NamedResponses", "NamedParameters", "NamedSecuritySchemes", "SecurityScheme", "XCodeSample", "XCodeSampleList", "XServerList", "XServer", "Server", "ServerList", "ServerVariable", "ServerVariablesMap", "Callback", "CallbacksMap", "RequestBody", "MediaTypesMap", "MediaType", "Encoding", "EncodingMap", "HeadersMap", "Link", "DiscriminatorMapping", "Discriminator", "Components", "LinksMap", "NamedExamples", "NamedRequestBodies", "NamedHeaders", "NamedLinks", "NamedCallbacks", "ImplicitFlow", "PasswordFlow", "ClientCredentials", "AuthorizationCode", "OAuth2Flows", "XUsePkce", "WebhooksMap", "XMetaData", "PatternProperties", "NamedPathItems", "DependentRequired", "HttpServerBinding", "HttpChannelBinding", "HttpMessageBinding", "HttpOperationBinding", "WsServerBinding", "WsChannelBinding", "WsMessageBinding", "WsOperationBinding", "KafkaServerBinding", "KafkaTopicConfiguration", "KafkaChannelBinding", "KafkaMessageBinding", "KafkaOperationBinding", "AnypointmqServerBinding", "AnypointmqChannelBinding", "AnypointmqMessageBinding", "AnypointmqOperationBinding", "AmqpServerBinding", "AmqpChannelBinding", "AmqpMessageBinding", "AmqpOperationBinding", "Amqp1ServerBinding", "Amqp1ChannelBinding", "Amqp1MessageBinding", "Amqp1OperationBinding", "MqttServerBindingLastWill", "MqttServerBinding", "MqttChannelBinding", "MqttMessageBinding", "MqttOperationBinding", "Mqtt5ServerBinding", "Mqtt5ChannelBinding", "Mqtt5MessageBinding", "Mqtt5OperationBinding", "NatsServerBinding", "NatsChannelBinding", "NatsMessageBinding", "NatsOperationBinding", "JmsServerBinding", "JmsChannelBinding", "JmsMessageBinding", "JmsOperationBinding", "SolaceServerBinding", "SolaceChannelBinding", "SolaceMessageBinding", "SolaceDestination", "SolaceOperationBinding", "StompServerBinding", "StompChannelBinding", "StompMessageBinding", "StompOperationBinding", "RedisServerBinding", "RedisChannelBinding", "RedisMessageBinding", "RedisOperationBinding", "MercureServerBinding", "MercureChannelBinding", "MercureMessageBinding", "MercureOperationBinding", "ServerBindings", "ChannelBindings", "MessageBindings", "OperationBindings", "ServerMap", "ChannelMap", "Channel", "ParametersMap", "MessageExample", "NamedMessages", "NamedMessageTraits", "NamedOperationTraits", "NamedCorrelationIds", "SecuritySchemeFlows", "Message", "OperationTrait", "OperationTraitList", "MessageTrait", "MessageTraitList", "MessageExampleList", "CorrelationId", "Dependencies", "OperationReply", "OperationReplyAddress", "NamedTags", "NamedExternalDocs", "NamedChannels", "NamedOperations", "NamedOperationReplies", "NamedOperationRelyAddresses", "SecuritySchemeList", "MessageList", "SourceDescriptions", "OpenAPISourceDescription", "ArazzoSourceDescription", "Parameters", "ReusableObject", "Workflows", "Workflow", "Steps", "Step", "Replacement", "ExtendedOperation", "Outputs", "CriterionObject", "XPathCriterion", "JSONPathCriterion", "SuccessActionObject", "OnSuccessActionList", "FailureActionObject", "OnFailureActionList", "NamedInputs", "NamedSuccessActions", "NamedFailureActions", "SpecExtension". +\`type\` can be one of the following only: "any", "Root", "Tag", "TagList", "TagGroups", "TagGroup", "ExternalDocs", "Example", "ExamplesMap", "EnumDescriptions", "SecurityRequirement", "SecurityRequirementList", "Info", "Contact", "License", "Logo", "Paths", "PathItem", "Parameter", "ParameterItems", "ParameterList", "Operation", "Examples", "Header", "Responses", "Response", "Schema", "Xml", "SchemaProperties", "NamedSchemas", "NamedResponses", "NamedParameters", "NamedSecuritySchemes", "SecurityScheme", "XCodeSample", "XCodeSampleList", "XServerList", "XServer", "Server", "ServerList", "ServerVariable", "ServerVariablesMap", "Callback", "CallbacksMap", "RequestBody", "MediaTypesMap", "MediaType", "Encoding", "EncodingMap", "HeadersMap", "Link", "DiscriminatorMapping", "Discriminator", "Components", "LinksMap", "NamedExamples", "NamedRequestBodies", "NamedHeaders", "NamedLinks", "NamedCallbacks", "ImplicitFlow", "PasswordFlow", "ClientCredentials", "AuthorizationCode", "OAuth2Flows", "XUsePkce", "WebhooksMap", "XMetaData", "PatternProperties", "NamedPathItems", "DependentRequired", "HttpServerBinding", "HttpChannelBinding", "HttpMessageBinding", "HttpOperationBinding", "WsServerBinding", "WsChannelBinding", "WsMessageBinding", "WsOperationBinding", "KafkaServerBinding", "KafkaTopicConfiguration", "KafkaChannelBinding", "KafkaMessageBinding", "KafkaOperationBinding", "AnypointmqServerBinding", "AnypointmqChannelBinding", "AnypointmqMessageBinding", "AnypointmqOperationBinding", "AmqpServerBinding", "AmqpChannelBinding", "AmqpMessageBinding", "AmqpOperationBinding", "Amqp1ServerBinding", "Amqp1ChannelBinding", "Amqp1MessageBinding", "Amqp1OperationBinding", "MqttServerBindingLastWill", "MqttServerBinding", "MqttChannelBinding", "MqttMessageBinding", "MqttOperationBinding", "Mqtt5ServerBinding", "Mqtt5ChannelBinding", "Mqtt5MessageBinding", "Mqtt5OperationBinding", "NatsServerBinding", "NatsChannelBinding", "NatsMessageBinding", "NatsOperationBinding", "JmsServerBinding", "JmsChannelBinding", "JmsMessageBinding", "JmsOperationBinding", "SolaceServerBinding", "SolaceChannelBinding", "SolaceMessageBinding", "SolaceDestination", "SolaceOperationBinding", "StompServerBinding", "StompChannelBinding", "StompMessageBinding", "StompOperationBinding", "RedisServerBinding", "RedisChannelBinding", "RedisMessageBinding", "RedisOperationBinding", "MercureServerBinding", "MercureChannelBinding", "MercureMessageBinding", "MercureOperationBinding", "ServerBindings", "ChannelBindings", "MessageBindings", "OperationBindings", "ServerMap", "ChannelMap", "Channel", "ParametersMap", "MessageExample", "NamedMessages", "NamedMessageTraits", "NamedOperationTraits", "NamedCorrelationIds", "SecuritySchemeFlows", "Message", "OperationTrait", "OperationTraitList", "MessageTrait", "MessageTraitList", "MessageExampleList", "CorrelationId", "Dependencies", "OperationReply", "OperationReplyAddress", "NamedTags", "NamedExternalDocs", "NamedChannels", "NamedOperations", "NamedOperationReplies", "NamedOperationRelyAddresses", "SecuritySchemeList", "MessageList", "SourceDescriptions", "OpenAPISourceDescription", "ArazzoSourceDescription", "Parameters", "ReusableObject", "Workflows", "Workflow", "Steps", "Step", "Replacement", "ExtendedOperation", "Outputs", "CriterionObject", "XPathCriterion", "JSONPathCriterion", "SuccessActionObject", "OnSuccessActionList", "FailureActionObject", "OnFailureActionList", "NamedInputs", "NamedSuccessActions", "NamedFailureActions", "Actions", "Action", "SpecExtension". 8 | rule/metadata-lifecycle: 9 | subject: diff --git a/__tests__/lint-config/invalid-config-assertation-config-type/snapshot.js b/__tests__/lint-config/invalid-config-assertation-config-type/snapshot.js index d7757a3731..3ea63fc899 100644 --- a/__tests__/lint-config/invalid-config-assertation-config-type/snapshot.js +++ b/__tests__/lint-config/invalid-config-assertation-config-type/snapshot.js @@ -6,7 +6,7 @@ exports[`E2E lint-config test with option: { dirName: 'invalid-config-assertatio The 'assert/' syntax in assert/path-item-mutually-required is deprecated. Update your configuration to use 'rule/' instead. Examples and more information: https://redocly.com/docs/cli/rules/configurable-rules/ [1] .redocly.yaml:9:17 at #/rules/assert~1path-item-mutually-required/where/0/subject/type -\`type\` can be one of the following only: "any", "Root", "Tag", "TagList", "TagGroups", "TagGroup", "ExternalDocs", "Example", "ExamplesMap", "EnumDescriptions", "SecurityRequirement", "SecurityRequirementList", "Info", "Contact", "License", "Logo", "Paths", "PathItem", "Parameter", "ParameterItems", "ParameterList", "Operation", "Examples", "Header", "Responses", "Response", "Schema", "Xml", "SchemaProperties", "NamedSchemas", "NamedResponses", "NamedParameters", "NamedSecuritySchemes", "SecurityScheme", "XCodeSample", "XCodeSampleList", "XServerList", "XServer", "Server", "ServerList", "ServerVariable", "ServerVariablesMap", "Callback", "CallbacksMap", "RequestBody", "MediaTypesMap", "MediaType", "Encoding", "EncodingMap", "HeadersMap", "Link", "DiscriminatorMapping", "Discriminator", "Components", "LinksMap", "NamedExamples", "NamedRequestBodies", "NamedHeaders", "NamedLinks", "NamedCallbacks", "ImplicitFlow", "PasswordFlow", "ClientCredentials", "AuthorizationCode", "OAuth2Flows", "XUsePkce", "WebhooksMap", "PatternProperties", "NamedPathItems", "DependentRequired", "HttpServerBinding", "HttpChannelBinding", "HttpMessageBinding", "HttpOperationBinding", "WsServerBinding", "WsChannelBinding", "WsMessageBinding", "WsOperationBinding", "KafkaServerBinding", "KafkaTopicConfiguration", "KafkaChannelBinding", "KafkaMessageBinding", "KafkaOperationBinding", "AnypointmqServerBinding", "AnypointmqChannelBinding", "AnypointmqMessageBinding", "AnypointmqOperationBinding", "AmqpServerBinding", "AmqpChannelBinding", "AmqpMessageBinding", "AmqpOperationBinding", "Amqp1ServerBinding", "Amqp1ChannelBinding", "Amqp1MessageBinding", "Amqp1OperationBinding", "MqttServerBindingLastWill", "MqttServerBinding", "MqttChannelBinding", "MqttMessageBinding", "MqttOperationBinding", "Mqtt5ServerBinding", "Mqtt5ChannelBinding", "Mqtt5MessageBinding", "Mqtt5OperationBinding", "NatsServerBinding", "NatsChannelBinding", "NatsMessageBinding", "NatsOperationBinding", "JmsServerBinding", "JmsChannelBinding", "JmsMessageBinding", "JmsOperationBinding", "SolaceServerBinding", "SolaceChannelBinding", "SolaceMessageBinding", "SolaceDestination", "SolaceOperationBinding", "StompServerBinding", "StompChannelBinding", "StompMessageBinding", "StompOperationBinding", "RedisServerBinding", "RedisChannelBinding", "RedisMessageBinding", "RedisOperationBinding", "MercureServerBinding", "MercureChannelBinding", "MercureMessageBinding", "MercureOperationBinding", "ServerBindings", "ChannelBindings", "MessageBindings", "OperationBindings", "ServerMap", "ChannelMap", "Channel", "ParametersMap", "MessageExample", "NamedMessages", "NamedMessageTraits", "NamedOperationTraits", "NamedCorrelationIds", "SecuritySchemeFlows", "Message", "OperationTrait", "OperationTraitList", "MessageTrait", "MessageTraitList", "MessageExampleList", "CorrelationId", "Dependencies", "OperationReply", "OperationReplyAddress", "NamedTags", "NamedExternalDocs", "NamedChannels", "NamedOperations", "NamedOperationReplies", "NamedOperationRelyAddresses", "SecuritySchemeList", "MessageList", "SourceDescriptions", "OpenAPISourceDescription", "ArazzoSourceDescription", "Parameters", "ReusableObject", "Workflows", "Workflow", "Steps", "Step", "Replacement", "ExtendedOperation", "Outputs", "CriterionObject", "XPathCriterion", "JSONPathCriterion", "SuccessActionObject", "OnSuccessActionList", "FailureActionObject", "OnFailureActionList", "NamedInputs", "NamedSuccessActions", "NamedFailureActions", "SpecExtension". +\`type\` can be one of the following only: "any", "Root", "Tag", "TagList", "TagGroups", "TagGroup", "ExternalDocs", "Example", "ExamplesMap", "EnumDescriptions", "SecurityRequirement", "SecurityRequirementList", "Info", "Contact", "License", "Logo", "Paths", "PathItem", "Parameter", "ParameterItems", "ParameterList", "Operation", "Examples", "Header", "Responses", "Response", "Schema", "Xml", "SchemaProperties", "NamedSchemas", "NamedResponses", "NamedParameters", "NamedSecuritySchemes", "SecurityScheme", "XCodeSample", "XCodeSampleList", "XServerList", "XServer", "Server", "ServerList", "ServerVariable", "ServerVariablesMap", "Callback", "CallbacksMap", "RequestBody", "MediaTypesMap", "MediaType", "Encoding", "EncodingMap", "HeadersMap", "Link", "DiscriminatorMapping", "Discriminator", "Components", "LinksMap", "NamedExamples", "NamedRequestBodies", "NamedHeaders", "NamedLinks", "NamedCallbacks", "ImplicitFlow", "PasswordFlow", "ClientCredentials", "AuthorizationCode", "OAuth2Flows", "XUsePkce", "WebhooksMap", "PatternProperties", "NamedPathItems", "DependentRequired", "HttpServerBinding", "HttpChannelBinding", "HttpMessageBinding", "HttpOperationBinding", "WsServerBinding", "WsChannelBinding", "WsMessageBinding", "WsOperationBinding", "KafkaServerBinding", "KafkaTopicConfiguration", "KafkaChannelBinding", "KafkaMessageBinding", "KafkaOperationBinding", "AnypointmqServerBinding", "AnypointmqChannelBinding", "AnypointmqMessageBinding", "AnypointmqOperationBinding", "AmqpServerBinding", "AmqpChannelBinding", "AmqpMessageBinding", "AmqpOperationBinding", "Amqp1ServerBinding", "Amqp1ChannelBinding", "Amqp1MessageBinding", "Amqp1OperationBinding", "MqttServerBindingLastWill", "MqttServerBinding", "MqttChannelBinding", "MqttMessageBinding", "MqttOperationBinding", "Mqtt5ServerBinding", "Mqtt5ChannelBinding", "Mqtt5MessageBinding", "Mqtt5OperationBinding", "NatsServerBinding", "NatsChannelBinding", "NatsMessageBinding", "NatsOperationBinding", "JmsServerBinding", "JmsChannelBinding", "JmsMessageBinding", "JmsOperationBinding", "SolaceServerBinding", "SolaceChannelBinding", "SolaceMessageBinding", "SolaceDestination", "SolaceOperationBinding", "StompServerBinding", "StompChannelBinding", "StompMessageBinding", "StompOperationBinding", "RedisServerBinding", "RedisChannelBinding", "RedisMessageBinding", "RedisOperationBinding", "MercureServerBinding", "MercureChannelBinding", "MercureMessageBinding", "MercureOperationBinding", "ServerBindings", "ChannelBindings", "MessageBindings", "OperationBindings", "ServerMap", "ChannelMap", "Channel", "ParametersMap", "MessageExample", "NamedMessages", "NamedMessageTraits", "NamedOperationTraits", "NamedCorrelationIds", "SecuritySchemeFlows", "Message", "OperationTrait", "OperationTraitList", "MessageTrait", "MessageTraitList", "MessageExampleList", "CorrelationId", "Dependencies", "OperationReply", "OperationReplyAddress", "NamedTags", "NamedExternalDocs", "NamedChannels", "NamedOperations", "NamedOperationReplies", "NamedOperationRelyAddresses", "SecuritySchemeList", "MessageList", "SourceDescriptions", "OpenAPISourceDescription", "ArazzoSourceDescription", "Parameters", "ReusableObject", "Workflows", "Workflow", "Steps", "Step", "Replacement", "ExtendedOperation", "Outputs", "CriterionObject", "XPathCriterion", "JSONPathCriterion", "SuccessActionObject", "OnSuccessActionList", "FailureActionObject", "OnFailureActionList", "NamedInputs", "NamedSuccessActions", "NamedFailureActions", "Actions", "Action", "SpecExtension". 7 | where: 8 | - subject: diff --git a/packages/cli/src/__tests__/commands/lint.test.ts b/packages/cli/src/__tests__/commands/lint.test.ts index cf76c6b832..2c2e21360e 100644 --- a/packages/cli/src/__tests__/commands/lint.test.ts +++ b/packages/cli/src/__tests__/commands/lint.test.ts @@ -58,7 +58,7 @@ describe('handleLint', () => { getMergedConfigMock.mockReset(); }); - describe('loadConfig and getEnrtypoints stage', () => { + describe('loadConfig and getEntrypoints stage', () => { it('should fail if config file does not exist', async () => { await commandWrapper(handleLint)({ ...argvMock, config: 'config.yaml' }); expect(exitWithError).toHaveBeenCalledWith( diff --git a/packages/cli/src/wrapper.ts b/packages/cli/src/wrapper.ts index df27a11675..46fb409dda 100644 --- a/packages/cli/src/wrapper.ts +++ b/packages/cli/src/wrapper.ts @@ -38,6 +38,8 @@ export function commandWrapper( ? 'asyncapi' : document?.arazzo ? 'arazzo' + : document?.overlay + ? 'overlay' : undefined; if (specKeyword) { specFullVersion = document[specKeyword] as string; diff --git a/packages/core/src/bundle.ts b/packages/core/src/bundle.ts index 0f60e75fd1..4ed20e33b9 100755 --- a/packages/core/src/bundle.ts +++ b/packages/core/src/bundle.ts @@ -311,6 +311,11 @@ export function mapTypeToComponent(typeName: string, version: SpecMajorVersion) default: return null; } + case SpecMajorVersion.Overlay1: + switch (typeName) { + default: + return null; + } } } diff --git a/packages/core/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap b/packages/core/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap index 5686ce079b..cd40fe15ad 100644 --- a/packages/core/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +++ b/packages/core/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap @@ -214,6 +214,11 @@ exports[`resolveConfig should ignore minimal from the root and read local file 1 "tag-description": "warn", "tags-alphabetical": "off", }, + "overlay1Decorators": {}, + "overlay1Preprocessors": {}, + "overlay1Rules": { + "info-contact": "off", + }, "preprocessors": {}, "recommendedFallback": false, "rules": { @@ -443,6 +448,11 @@ exports[`resolveStyleguideConfig should resolve extends with local file config w "tag-description": "warn", "tags-alphabetical": "off", }, + "overlay1Decorators": {}, + "overlay1Preprocessors": {}, + "overlay1Rules": { + "info-contact": "off", + }, "preprocessors": {}, "recommendedFallback": undefined, "rules": { diff --git a/packages/core/src/config/__tests__/__snapshots__/config.test.ts.snap b/packages/core/src/config/__tests__/__snapshots__/config.test.ts.snap index 3eb648f561..2fc059707e 100644 --- a/packages/core/src/config/__tests__/__snapshots__/config.test.ts.snap +++ b/packages/core/src/config/__tests__/__snapshots__/config.test.ts.snap @@ -32,6 +32,11 @@ StyleguideConfig { "oas3_0": {}, "oas3_1": {}, }, + "overlay1": { + "oas2": {}, + "oas3_0": {}, + "oas3_1": {}, + }, }, "doNotResolveExamples": false, "extendPaths": [], @@ -76,6 +81,11 @@ StyleguideConfig { "oas3_0": {}, "oas3_1": {}, }, + "overlay1": { + "oas2": {}, + "oas3_0": {}, + "oas3_1": {}, + }, }, "rawConfig": { "_usedRules": Set {}, @@ -203,6 +213,20 @@ StyleguideConfig { "operation-summary": "error", }, }, + "overlay1": { + "oas2": { + "no-empty-servers": "error", + "operation-summary": "error", + }, + "oas3_0": { + "no-empty-servers": "error", + "operation-summary": "error", + }, + "oas3_1": { + "no-empty-servers": "error", + "operation-summary": "error", + }, + }, }, } `; diff --git a/packages/core/src/config/__tests__/config.test.ts b/packages/core/src/config/__tests__/config.test.ts index 9d3193c4b3..8901c40005 100644 --- a/packages/core/src/config/__tests__/config.test.ts +++ b/packages/core/src/config/__tests__/config.test.ts @@ -123,6 +123,7 @@ describe('getMergedConfig', () => { "oas2": {}, "oas3_0": {}, "oas3_1": {}, + "overlay1": {}, }, "doNotResolveExamples": false, "extendPaths": [], @@ -136,6 +137,7 @@ describe('getMergedConfig', () => { "oas2": {}, "oas3_0": {}, "oas3_1": {}, + "overlay1": {}, }, "rawConfig": { "extendPaths": [], @@ -164,6 +166,9 @@ describe('getMergedConfig', () => { "oas3_1": { "operation-summary": "warn", }, + "overlay1": { + "operation-summary": "warn", + }, }, }, "telemetry": "on", @@ -238,6 +243,7 @@ describe('getMergedConfig', () => { "oas2": {}, "oas3_0": {}, "oas3_1": {}, + "overlay1": {}, }, "doNotResolveExamples": false, "extendPaths": [], @@ -251,6 +257,7 @@ describe('getMergedConfig', () => { "oas2": {}, "oas3_0": {}, "oas3_1": {}, + "overlay1": {}, }, "rawConfig": { "extendPaths": [], @@ -287,6 +294,10 @@ describe('getMergedConfig', () => { "no-empty-servers": "error", "operation-summary": "error", }, + "overlay1": { + "no-empty-servers": "error", + "operation-summary": "error", + }, }, }, "telemetry": "on", diff --git a/packages/core/src/config/all.ts b/packages/core/src/config/all.ts index 10fd51f19c..7251d04644 100644 --- a/packages/core/src/config/all.ts +++ b/packages/core/src/config/all.ts @@ -226,6 +226,9 @@ const all: PluginStyleguideConfig<'built-in'> = { 'workflowId-unique': 'error', 'workflow-dependsOn': 'error', }, + overlay1Rules: { + 'info-contact': 'error', + }, }; export default all; diff --git a/packages/core/src/config/builtIn.ts b/packages/core/src/config/builtIn.ts index b2cc2406d7..7db8088617 100644 --- a/packages/core/src/config/builtIn.ts +++ b/packages/core/src/config/builtIn.ts @@ -8,11 +8,13 @@ import { rules as oas2Rules, preprocessors as oas2Preprocessors } from '../rules import { rules as async2Rules, preprocessors as async2Preprocessors } from '../rules/async2'; import { rules as async3Rules, preprocessors as async3Preprocessors } from '../rules/async3'; import { rules as arazzo1Rules, preprocessors as arazzoPreprocessors } from '../rules/arazzo'; +import { rules as overlay1Rules, preprocessors as overlay1Preprocessors } from '../rules/overlay1'; import { decorators as oas3Decorators } from '../decorators/oas3'; import { decorators as oas2Decorators } from '../decorators/oas2'; import { decorators as async2Decorators } from '../decorators/async2'; import { decorators as async3Decorators } from '../decorators/async3'; import { decorators as arazzo1Decorators } from '../decorators/arazzo'; +import { decorators as overlay1Decorators } from '../decorators/overlay1'; import type { StyleguideRawConfig, Plugin } from './types'; @@ -35,6 +37,7 @@ export const defaultPlugin: Plugin<'built-in'> = { async2: async2Rules, async3: async3Rules, arazzo1: arazzo1Rules, + overlay1: overlay1Rules, }, preprocessors: { oas3: oas3Preprocessors, @@ -42,6 +45,7 @@ export const defaultPlugin: Plugin<'built-in'> = { async2: async2Preprocessors, async3: async3Preprocessors, arazzo1: arazzoPreprocessors, + overlay1: overlay1Preprocessors, }, decorators: { oas3: oas3Decorators, @@ -49,6 +53,7 @@ export const defaultPlugin: Plugin<'built-in'> = { async2: async2Decorators, async3: async3Decorators, arazzo1: arazzo1Decorators, + overlay1: overlay1Decorators, }, configs: builtInConfigs, }; diff --git a/packages/core/src/config/config-resolvers.ts b/packages/core/src/config/config-resolvers.ts index 922e89d602..c3721bc913 100644 --- a/packages/core/src/config/config-resolvers.ts +++ b/packages/core/src/config/config-resolvers.ts @@ -250,9 +250,16 @@ export async function resolvePlugins( }; if (pluginModule.rules) { - if (!pluginModule.rules.oas3 && !pluginModule.rules.oas2 && !pluginModule.rules.async2) { + if ( + !pluginModule.rules.oas3 && + !pluginModule.rules.oas2 && + !pluginModule.rules.async2 && + !pluginModule.rules.async3 && + !pluginModule.rules.arazzo1 && + !pluginModule.rules.overlay1 + ) { throw new Error( - `Plugin rules must have \`oas3\`, \`oas2\`, \`async2\`, \`async3\` or \`arazzo\` rules "${p}.` + `Plugin rules must have \`oas3\`, \`oas2\`, \`async2\`, \`async3\`, \`arazzo\`, or \`overlay1\` rules "${p}.` ); } plugin.rules = {}; @@ -271,6 +278,9 @@ export async function resolvePlugins( if (pluginModule.rules.arazzo1) { plugin.rules.arazzo1 = prefixRules(pluginModule.rules.arazzo1, id); } + if (pluginModule.rules.overlay1) { + plugin.rules.overlay1 = prefixRules(pluginModule.rules.overlay1, id); + } } if (pluginModule.preprocessors) { if ( @@ -278,10 +288,11 @@ export async function resolvePlugins( !pluginModule.preprocessors.oas2 && !pluginModule.preprocessors.async2 && !pluginModule.preprocessors.async3 && - !pluginModule.preprocessors.arazzo1 + !pluginModule.preprocessors.arazzo1 && + !pluginModule.preprocessors.overlay1 ) { throw new Error( - `Plugin \`preprocessors\` must have \`oas3\`, \`oas2\` or \`async2\` preprocessors "${p}.` + `Plugin \`preprocessors\` must have \`oas3\`, \`oas2\`, \`async2\`, \`async3\`, \`arazzo1\`, or \`overlay1\` preprocessors "${p}.` ); } plugin.preprocessors = {}; @@ -300,6 +311,9 @@ export async function resolvePlugins( if (pluginModule.preprocessors.arazzo1) { plugin.preprocessors.arazzo1 = prefixRules(pluginModule.preprocessors.arazzo1, id); } + if (pluginModule.preprocessors.overlay1) { + plugin.preprocessors.overlay1 = prefixRules(pluginModule.preprocessors.overlay1, id); + } } if (pluginModule.decorators) { @@ -308,10 +322,11 @@ export async function resolvePlugins( !pluginModule.decorators.oas2 && !pluginModule.decorators.async2 && !pluginModule.decorators.async3 && - !pluginModule.decorators.arazzo1 + !pluginModule.decorators.arazzo1 && + !pluginModule.decorators.overlay1 ) { throw new Error( - `Plugin \`decorators\` must have \`oas3\`, \`oas2\`, \`async2\` or \`async3\` decorators "${p}.` + `Plugin \`decorators\` must have \`oas3\`, \`oas2\`, \`async2\`, \`async3\`, \`arazzo1\`, or \`overlay1\` decorators "${p}.` ); } plugin.decorators = {}; @@ -330,6 +345,9 @@ export async function resolvePlugins( if (pluginModule.decorators.arazzo1) { plugin.decorators.arazzo1 = prefixRules(pluginModule.decorators.arazzo1, id); } + if (pluginModule.decorators.overlay1) { + plugin.decorators.overlay1 = prefixRules(pluginModule.decorators.overlay1, id); + } } if (pluginModule.assertions) { @@ -520,6 +538,10 @@ function getMergedRawStyleguideConfig( async2Rules: { ...rootStyleguideConfig?.async2Rules, ...apiStyleguideConfig?.async2Rules }, async3Rules: { ...rootStyleguideConfig?.async3Rules, ...apiStyleguideConfig?.async3Rules }, arazzo1Rules: { ...rootStyleguideConfig?.arazzo1Rules, ...apiStyleguideConfig?.arazzo1Rules }, + overlay1Rules: { + ...rootStyleguideConfig?.overlay1Rules, + ...apiStyleguideConfig?.overlay1Rules, + }, preprocessors: { ...rootStyleguideConfig?.preprocessors, ...apiStyleguideConfig?.preprocessors, @@ -536,6 +558,10 @@ function getMergedRawStyleguideConfig( ...rootStyleguideConfig?.oas3_1Preprocessors, ...apiStyleguideConfig?.oas3_1Preprocessors, }, + overlay1Preprocessors: { + ...rootStyleguideConfig?.overlay1Preprocessors, + ...apiStyleguideConfig?.overlay1Preprocessors, + }, decorators: { ...rootStyleguideConfig?.decorators, ...apiStyleguideConfig?.decorators }, oas2Decorators: { ...rootStyleguideConfig?.oas2Decorators, @@ -549,6 +575,10 @@ function getMergedRawStyleguideConfig( ...rootStyleguideConfig?.oas3_1Decorators, ...apiStyleguideConfig?.oas3_1Decorators, }, + overlay1Decorators: { + ...rootStyleguideConfig?.overlay1Decorators, + ...apiStyleguideConfig?.overlay1Decorators, + }, recommendedFallback: apiStyleguideConfig?.extends ? false : rootStyleguideConfig.recommendedFallback, diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 782bae3450..f34a5d3f8b 100755 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -14,6 +14,7 @@ import type { Async2RuleSet, Async3RuleSet, Arazzo1RuleSet, + Overlay1RuleSet, } from '../oas-types'; import type { NodeType } from '../types'; import type { @@ -75,6 +76,7 @@ export class StyleguideConfig { 'async2Rules', 'async3Rules', 'arazzo1Rules', + 'overlay1Rules', ]; replaceSpecWithStruct(ruleGroups, rawConfig); @@ -86,6 +88,7 @@ export class StyleguideConfig { [SpecVersion.Async2]: { ...rawConfig.rules, ...rawConfig.async2Rules }, [SpecVersion.Async3]: { ...rawConfig.rules, ...rawConfig.async3Rules }, [SpecVersion.Arazzo1]: { ...rawConfig.rules, ...rawConfig.arazzo1Rules }, + [SpecVersion.Overlay1]: { ...rawConfig.rules, ...rawConfig.overlay1Rules }, }; this.preprocessors = { @@ -95,6 +98,7 @@ export class StyleguideConfig { [SpecVersion.Async2]: { ...rawConfig.preprocessors, ...rawConfig.async2Preprocessors }, [SpecVersion.Async3]: { ...rawConfig.preprocessors, ...rawConfig.async3Preprocessors }, [SpecVersion.Arazzo1]: { ...rawConfig.arazzo1Preprocessors }, + [SpecVersion.Overlay1]: { ...rawConfig.preprocessors, ...rawConfig.overlay1Preprocessors }, }; this.decorators = { @@ -104,6 +108,7 @@ export class StyleguideConfig { [SpecVersion.Async2]: { ...rawConfig.decorators, ...rawConfig.async2Decorators }, [SpecVersion.Async3]: { ...rawConfig.decorators, ...rawConfig.async3Decorators }, [SpecVersion.Arazzo1]: { ...rawConfig.arazzo1Decorators }, + [SpecVersion.Overlay1]: { ...rawConfig.decorators, ...rawConfig.overlay1Decorators }, }; this.extendPaths = rawConfig.extendPaths || []; @@ -207,6 +212,10 @@ export class StyleguideConfig { if (!plugin.typeExtension.arazzo1) continue; extendedTypes = plugin.typeExtension.arazzo1(extendedTypes, version); break; + case SpecVersion.Overlay1: + if (!plugin.typeExtension.overlay1) continue; + extendedTypes = plugin.typeExtension.overlay1(extendedTypes, version); + break; default: throw new Error('Not implemented'); } @@ -328,6 +337,17 @@ export class StyleguideConfig { (p) => p.decorators?.arazzo1 && arazzo1Rules.push(p.decorators.arazzo1) ); return arazzo1Rules; + case SpecMajorVersion.Overlay1: + // eslint-disable-next-line no-case-declarations + const overlay1Rules: Overlay1RuleSet[] = []; + this.plugins.forEach( + (p) => p.preprocessors?.overlay1 && overlay1Rules.push(p.preprocessors.overlay1) + ); + this.plugins.forEach((p) => p.rules?.overlay1 && overlay1Rules.push(p.rules.overlay1)); + this.plugins.forEach( + (p) => p.decorators?.overlay1 && overlay1Rules.push(p.decorators.overlay1) + ); + return overlay1Rules; } } diff --git a/packages/core/src/config/minimal.ts b/packages/core/src/config/minimal.ts index e91cac8ebc..a3f258f6af 100644 --- a/packages/core/src/config/minimal.ts +++ b/packages/core/src/config/minimal.ts @@ -202,6 +202,9 @@ const minimal: PluginStyleguideConfig<'built-in'> = { 'workflowId-unique': 'error', 'workflow-dependsOn': 'off', }, + overlay1Rules: { + 'info-contact': 'off', + }, }; export default minimal; diff --git a/packages/core/src/config/recommended-strict.ts b/packages/core/src/config/recommended-strict.ts index d9689fecef..e17aaace17 100644 --- a/packages/core/src/config/recommended-strict.ts +++ b/packages/core/src/config/recommended-strict.ts @@ -202,6 +202,9 @@ const recommendedStrict: PluginStyleguideConfig<'built-in'> = { 'workflowId-unique': 'error', 'workflow-dependsOn': 'error', }, + overlay1Rules: { + 'info-contact': 'off', + }, }; export default recommendedStrict; diff --git a/packages/core/src/config/recommended.ts b/packages/core/src/config/recommended.ts index 6605b76f91..e4b0e39054 100644 --- a/packages/core/src/config/recommended.ts +++ b/packages/core/src/config/recommended.ts @@ -202,6 +202,9 @@ const recommended: PluginStyleguideConfig<'built-in'> = { 'workflowId-unique': 'error', 'workflow-dependsOn': 'error', }, + overlay1Rules: { + 'info-contact': 'off', + }, }; export default recommended; diff --git a/packages/core/src/config/rules.ts b/packages/core/src/config/rules.ts index 02b722d47a..066927cfe1 100644 --- a/packages/core/src/config/rules.ts +++ b/packages/core/src/config/rules.ts @@ -6,6 +6,7 @@ import type { Async3RuleSet, Oas2RuleSet, Oas3RuleSet, + Overlay1RuleSet, SpecVersion, } from '../oas-types'; import type { StyleguideConfig } from './config'; @@ -18,7 +19,14 @@ type InitializedRule = { }; export function initRules( - rules: (Oas3RuleSet | Oas2RuleSet | Async2RuleSet | Async3RuleSet | Arazzo1RuleSet)[], + rules: ( + | Oas3RuleSet + | Oas2RuleSet + | Async2RuleSet + | Async3RuleSet + | Arazzo1RuleSet + | Overlay1RuleSet + )[], config: StyleguideConfig, type: 'rules' | 'preprocessors' | 'decorators', oasVersion: SpecVersion diff --git a/packages/core/src/config/spec.ts b/packages/core/src/config/spec.ts index edc37a312b..79256e8626 100644 --- a/packages/core/src/config/spec.ts +++ b/packages/core/src/config/spec.ts @@ -25,6 +25,9 @@ const spec: PluginStyleguideConfig<'built-in'> = { 'no-criteria-xpath': 'off', 'criteria-unique': 'error', }, + overlay1Rules: { + 'info-contact': 'warn', + }, }; export default spec; diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index 465fb1f6a4..d20e460a20 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -19,6 +19,9 @@ import type { Arazzo1PreprocessorsSet, Arazzo1DecoratorsSet, RuleMap, + Overlay1PreprocessorsSet, + Overlay1DecoratorsSet, + Overlay1RuleSet, } from '../oas-types'; import type { NodeType } from '../types'; import type { SkipFunctionContext } from '../visitors'; @@ -53,6 +56,7 @@ export type StyleguideRawConfig = { async2Rules?: RuleMap; async3Rules?: RuleMap; arazzo1Rules?: RuleMap; + overlay1Rules?: RuleMap; preprocessors?: Record; oas2Preprocessors?: Record; @@ -61,6 +65,7 @@ export type StyleguideRawConfig = { async2Preprocessors?: Record; async3Preprocessors?: Record; arazzo1Preprocessors?: Record; + overlay1Preprocessors?: Record; decorators?: Record; oas2Decorators?: Record; @@ -69,6 +74,7 @@ export type StyleguideRawConfig = { async2Decorators?: Record; async3Decorators?: Record; arazzo1Decorators?: Record; + overlay1Decorators?: Record; }; export type ApiStyleguideRawConfig = Omit; @@ -87,6 +93,7 @@ export type PreprocessorsConfig = { async2?: Async2PreprocessorsSet; async3?: Async3PreprocessorsSet; arazzo1?: Arazzo1PreprocessorsSet; + overlay1?: Overlay1PreprocessorsSet; }; export type DecoratorsConfig = { @@ -95,6 +102,7 @@ export type DecoratorsConfig = { async2?: Async2DecoratorsSet; async3?: Async3DecoratorsSet; arazzo1?: Arazzo1DecoratorsSet; + overlay1?: Overlay1DecoratorsSet; }; export type TypesExtensionFn = ( @@ -110,6 +118,7 @@ export type RulesConfig = { async2?: Async2RuleSet; async3?: Async3RuleSet; arazzo1?: Arazzo1RuleSet; + overlay1?: Overlay1RuleSet; }; export type CustomRulesConfig = RulesConfig; @@ -278,6 +287,7 @@ export type RulesFields = | 'async2Rules' | 'async3Rules' | 'arazzo1Rules' + | 'overlay1Rules' | 'preprocessors' | 'oas2Preprocessors' | 'oas3_0Preprocessors' @@ -285,10 +295,12 @@ export type RulesFields = | 'async2Preprocessors' | 'async3Preprocessors' | 'arazzo1Preprocessors' + | 'overlay1Preprocessors' | 'decorators' | 'oas2Decorators' | 'oas3_0Decorators' | 'oas3_1Decorators' | 'async2Decorators' | 'async3Decorators' - | 'arazzo1Decorators'; + | 'arazzo1Decorators' + | 'overlay1Decorators'; diff --git a/packages/core/src/config/utils.ts b/packages/core/src/config/utils.ts index 8a74602493..5c2d421cbd 100644 --- a/packages/core/src/config/utils.ts +++ b/packages/core/src/config/utils.ts @@ -62,6 +62,7 @@ function extractFlatConfig< async2Rules, async3Rules, arazzo1Rules, + overlay1Rules, preprocessors, oas2Preprocessors, @@ -70,6 +71,7 @@ function extractFlatConfig< async2Preprocessors, async3Preprocessors, arazzo1Preprocessors, + overlay1Preprocessors, decorators, oas2Decorators, @@ -78,6 +80,7 @@ function extractFlatConfig< async2Decorators, async3Decorators, arazzo1Decorators, + overlay1Decorators, ...rawConfigRest }: T): { @@ -95,6 +98,7 @@ function extractFlatConfig< async2Rules, async3Rules, arazzo1Rules, + overlay1Rules, preprocessors, oas2Preprocessors, @@ -103,6 +107,7 @@ function extractFlatConfig< async2Preprocessors, async3Preprocessors, arazzo1Preprocessors, + overlay1Preprocessors, decorators, oas2Decorators, @@ -111,6 +116,7 @@ function extractFlatConfig< async2Decorators, async3Decorators, arazzo1Decorators, + overlay1Decorators, doNotResolveExamples: rawConfigRest.resolve?.doNotResolveExamples, }; @@ -170,6 +176,7 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) { async2Rules: {}, async3Rules: {}, arazzo1Rules: {}, + overlay1Rules: {}, preprocessors: {}, oas2Preprocessors: {}, @@ -178,6 +185,7 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) { async2Preprocessors: {}, async3Preprocessors: {}, arazzo1Preprocessors: {}, + overlay1Preprocessors: {}, decorators: {}, oas2Decorators: {}, @@ -186,6 +194,7 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) { async2Decorators: {}, async3Decorators: {}, arazzo1Decorators: {}, + overlay1Decorators: {}, plugins: [], pluginPaths: [], @@ -212,6 +221,8 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) { assignOnlyExistingConfig(result.async3Rules, rulesConf.rules); assignConfig(result.arazzo1Rules, rulesConf.arazzo1Rules); assignOnlyExistingConfig(result.arazzo1Rules, rulesConf.rules); + assignConfig(result.overlay1Rules, rulesConf.overlay1Rules); + assignOnlyExistingConfig(result.overlay1Rules, rulesConf.rules); assignConfig(result.preprocessors, rulesConf.preprocessors); assignConfig(result.oas2Preprocessors, rulesConf.oas2Preprocessors); @@ -226,6 +237,8 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) { assignOnlyExistingConfig(result.async3Preprocessors, rulesConf.preprocessors); assignConfig(result.arazzo1Preprocessors, rulesConf.arazzo1Preprocessors); assignOnlyExistingConfig(result.arazzo1Preprocessors, rulesConf.preprocessors); + assignConfig(result.overlay1Preprocessors, rulesConf.overlay1Preprocessors); + assignOnlyExistingConfig(result.overlay1Preprocessors, rulesConf.preprocessors); assignConfig(result.decorators, rulesConf.decorators); assignConfig(result.oas2Decorators, rulesConf.oas2Decorators); @@ -240,6 +253,8 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) { assignOnlyExistingConfig(result.async3Decorators, rulesConf.decorators); assignConfig(result.arazzo1Decorators, rulesConf.arazzo1Decorators); assignOnlyExistingConfig(result.arazzo1Decorators, rulesConf.decorators); + assignConfig(result.overlay1Decorators, rulesConf.overlay1Decorators); + assignOnlyExistingConfig(result.overlay1Decorators, rulesConf.decorators); result.plugins!.push(...(rulesConf.plugins || [])); result.pluginPaths!.push(...(rulesConf.pluginPaths || [])); diff --git a/packages/core/src/decorators/overlay1/index.ts b/packages/core/src/decorators/overlay1/index.ts new file mode 100644 index 0000000000..6ebd5e11e7 --- /dev/null +++ b/packages/core/src/decorators/overlay1/index.ts @@ -0,0 +1 @@ +export const decorators = {}; diff --git a/packages/core/src/lint.ts b/packages/core/src/lint.ts index 60072d40de..bf662a4795 100755 --- a/packages/core/src/lint.ts +++ b/packages/core/src/lint.ts @@ -21,6 +21,7 @@ import type { NestedVisitObject, Oas2Visitor, Oas3Visitor, + Overlay1Visitor, RuleInstanceConfig, } from './visitors'; import type { CollectFn } from './utils'; @@ -154,6 +155,8 @@ export async function lintConfig(opts: { | Async3Visitor[] | Arazzo1Visitor | Arazzo1Visitor[] + | Overlay1Visitor + | Overlay1Visitor[] >; })[] = [ { diff --git a/packages/core/src/oas-types.ts b/packages/core/src/oas-types.ts index bbdd719d1a..8bc0749a42 100644 --- a/packages/core/src/oas-types.ts +++ b/packages/core/src/oas-types.ts @@ -4,6 +4,7 @@ import { Oas3_1Types } from './types/oas3_1'; import { AsyncApi2Types } from './types/asyncapi2'; import { AsyncApi3Types } from './types/asyncapi3'; import { Arazzo1Types } from './types/arazzo'; +import { Overlay1Types } from './types/overlay'; import { isPlainObject } from './utils'; import { VERSION_PATTERN } from './typings/arazzo'; @@ -13,6 +14,7 @@ import type { BuiltInArazzo1RuleId, BuiltInOAS2RuleId, BuiltInOAS3RuleId, + BuiltInOverlay1RuleId, } from './types/redocly-yaml'; import type { Oas3Rule, @@ -25,6 +27,8 @@ import type { Async3Rule, Arazzo1Preprocessor, Arazzo1Rule, + Overlay1Preprocessor, + Overlay1Rule, } from './visitors'; export enum SpecVersion { @@ -34,6 +38,7 @@ export enum SpecVersion { Async2 = 'async2', Async3 = 'async3', Arazzo1 = 'arazzo1', + Overlay1 = 'overlay1', } export enum SpecMajorVersion { @@ -42,6 +47,7 @@ export enum SpecMajorVersion { Async2 = 'async2', Async3 = 'async3', Arazzo1 = 'arazzo1', + Overlay1 = 'overlay1', } const typesMap = { @@ -51,6 +57,7 @@ const typesMap = { [SpecVersion.Async2]: AsyncApi2Types, [SpecVersion.Async3]: AsyncApi3Types, [SpecVersion.Arazzo1]: Arazzo1Types, + [SpecVersion.Overlay1]: Overlay1Types, }; export type RuleMap = Record< @@ -83,17 +90,24 @@ export type Arazzo1RuleSet = RuleMap< T >; +export type Overlay1RuleSet = RuleMap< + BuiltInOverlay1RuleId | 'struct' | 'assertions', + Overlay1Rule, + T +>; export type Oas3PreprocessorsSet = Record; export type Oas2PreprocessorsSet = Record; export type Async2PreprocessorsSet = Record; export type Async3PreprocessorsSet = Record; export type Arazzo1PreprocessorsSet = Record; +export type Overlay1PreprocessorsSet = Record; export type Oas3DecoratorsSet = Record; export type Oas2DecoratorsSet = Record; export type Async2DecoratorsSet = Record; export type Async3DecoratorsSet = Record; export type Arazzo1DecoratorsSet = Record; +export type Overlay1DecoratorsSet = Record; export function detectSpec(root: unknown): SpecVersion { if (!isPlainObject(root)) { @@ -136,6 +150,10 @@ export function detectSpec(root: unknown): SpecVersion { return SpecVersion.Arazzo1; } + if (typeof root.overlay === 'string' && VERSION_PATTERN.test(root.overlay)) { + return SpecVersion.Overlay1; + } + throw new Error(`Unsupported specification`); } @@ -148,6 +166,8 @@ export function getMajorSpecVersion(version: SpecVersion): SpecMajorVersion { return SpecMajorVersion.Async3; } else if (version === SpecVersion.Arazzo1) { return SpecMajorVersion.Arazzo1; + } else if (version === SpecVersion.Overlay1) { + return SpecMajorVersion.Overlay1; } else { return SpecMajorVersion.OAS3; } diff --git a/packages/core/src/rules/common/assertions/index.ts b/packages/core/src/rules/common/assertions/index.ts index 00f689031f..53f35ad764 100644 --- a/packages/core/src/rules/common/assertions/index.ts +++ b/packages/core/src/rules/common/assertions/index.ts @@ -8,6 +8,7 @@ import type { Async3Visitor, Oas2Visitor, Oas3Visitor, + Overlay1Visitor, } from '../../../visitors'; import type { RuleSeverity } from '../../../config'; @@ -35,8 +36,14 @@ export type RawAssertion = AssertionDefinition & { export type Assertion = RawAssertion & { assertionId: string }; export const Assertions = (opts: Record) => { - const visitors: (Oas2Visitor | Oas3Visitor | Async2Visitor | Async3Visitor | Arazzo1Visitor)[] = - []; + const visitors: ( + | Oas2Visitor + | Oas3Visitor + | Async2Visitor + | Async3Visitor + | Arazzo1Visitor + | Overlay1Visitor + )[] = []; // As 'Assertions' has an array of asserts, // that array spreads into an 'opts' object on init rules phase here diff --git a/packages/core/src/rules/common/struct.ts b/packages/core/src/rules/common/struct.ts index d69697b17e..b148f37770 100644 --- a/packages/core/src/rules/common/struct.ts +++ b/packages/core/src/rules/common/struct.ts @@ -4,9 +4,22 @@ import { isRef } from '../../ref-utils'; import { isPlainObject } from '../../utils'; import type { UserContext } from '../../walk'; -import type { Oas3Rule, Oas2Rule, Async2Rule, Async3Rule, Arazzo1Rule } from '../../visitors'; - -export const Struct: Oas3Rule | Oas2Rule | Async2Rule | Async3Rule | Arazzo1Rule = () => { +import type { + Oas3Rule, + Oas2Rule, + Async2Rule, + Async3Rule, + Arazzo1Rule, + Overlay1Rule, +} from '../../visitors'; + +export const Struct: + | Oas3Rule + | Oas2Rule + | Async2Rule + | Async3Rule + | Arazzo1Rule + | Overlay1Rule = () => { return { any( node: any, diff --git a/packages/core/src/rules/overlay1/__tests__/info-contact.test.ts b/packages/core/src/rules/overlay1/__tests__/info-contact.test.ts new file mode 100644 index 0000000000..a5831e28e2 --- /dev/null +++ b/packages/core/src/rules/overlay1/__tests__/info-contact.test.ts @@ -0,0 +1,112 @@ +import { outdent } from 'outdent'; +import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils'; +import { lintDocument } from '../../../lint'; +import { BaseResolver } from '../../../resolve'; + +describe('Overlay 1.0 Description', () => { + it('should not report if the Contact Object is valid', async () => { + const document = parseYamlToDocument( + outdent` + overlay: 1.0.0 + info: + title: Example Overlay 1 definition. Valid. + version: '1.0' + contact: + name: API Support + url: http://www.example.com/support + email: support@example.com + extends: 'openapi.yaml' + actions: [] + `, + 'overlay.yaml' + ); + const results = await lintDocument({ + externalRefResolver: new BaseResolver(), + document, + config: await makeConfig({ + rules: { + 'info-contact': { severity: 'error' }, + }, + }), + }); + expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`); + }); + + it('should report if the Contact Object is not defined', async () => { + const document = parseYamlToDocument( + outdent` + overlay: 1.0.0 + info: + title: Example Overlay 1 definition. Invalid. + version: '1.0' + extends: 'openapi.yaml' + actions: [] + `, + 'overlay.yaml' + ); + const results = await lintDocument({ + externalRefResolver: new BaseResolver(), + document, + config: await makeConfig({ + rules: { + 'info-contact': { severity: 'error' }, + }, + }), + }); + expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(` + [ + { + "location": [ + { + "pointer": "#/info/contact", + "reportOnKey": true, + "source": "overlay.yaml", + }, + ], + "message": "Info object should contain \`contact\` field.", + "ruleId": "info-contact", + "severity": "error", + "suggest": [], + }, + ] + `); + }); + it('should report if the Overlay Document is invalid', async () => { + const document = parseYamlToDocument( + outdent` + overlay: 1.0.0 + info: + title: Example Overlay 1 definition. Invalid. + version: '1.0' + `, + 'overlay.yaml' + ); + const results = await lintDocument({ + externalRefResolver: new BaseResolver(), + document, + config: await makeConfig({ + rules: { + struct: { severity: 'error' }, + }, + }), + }); + expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(` + [ + { + "from": undefined, + "location": [ + { + "pointer": "#/", + "reportOnKey": true, + "source": "overlay.yaml", + }, + ], + "message": "The field \`actions\` must be present on this level.", + "ruleId": "struct", + "severity": "error", + "suggest": [], + }, + ] + `); + }); +}); diff --git a/packages/core/src/rules/overlay1/index.ts b/packages/core/src/rules/overlay1/index.ts new file mode 100644 index 0000000000..a5c5c2ceaa --- /dev/null +++ b/packages/core/src/rules/overlay1/index.ts @@ -0,0 +1,14 @@ +import { Assertions } from '../common/assertions'; +import { InfoContact } from '../common/info-contact'; +import { Struct } from '../common/struct'; + +import type { Overlay1RuleSet } from '../../oas-types'; +import type { Overlay1Rule } from '../../visitors'; + +export const rules: Overlay1RuleSet<'built-in'> = { + 'info-contact': InfoContact as Overlay1Rule, + struct: Struct as Overlay1Rule, + assertions: Assertions as Overlay1Rule, +}; + +export const preprocessors = {}; diff --git a/packages/core/src/types/redocly-yaml.ts b/packages/core/src/types/redocly-yaml.ts index c6d06e6b0e..77223bdd52 100644 --- a/packages/core/src/types/redocly-yaml.ts +++ b/packages/core/src/types/redocly-yaml.ts @@ -160,12 +160,17 @@ const builtInArazzo1Rules = [ export type BuiltInArazzo1RuleId = typeof builtInArazzo1Rules[number]; +const builtInOverlay1Rules = ['info-contact'] as const; + +export type BuiltInOverlay1RuleId = typeof builtInOverlay1Rules[number]; + const builtInRules = [ ...builtInOAS2Rules, ...builtInOAS3Rules, ...builtInAsync2Rules, ...builtInAsync3Rules, ...builtInArazzo1Rules, + ...builtInOverlay1Rules, 'spec', // TODO: depricated in favor of struct 'struct', ] as const; diff --git a/packages/core/src/visitors.ts b/packages/core/src/visitors.ts index 4dece6af4d..e4d9385242 100644 --- a/packages/core/src/visitors.ts +++ b/packages/core/src/visitors.ts @@ -70,6 +70,7 @@ import type { Step, Workflow, } from './typings/arazzo'; +import type { Overlay1Definition } from './typings/overlay'; export type SkipFunctionContext = Pick< UserContext, @@ -259,6 +260,10 @@ type ArazzoFlatVisitor = { Workflows?: VisitFunctionOrObject; }; +type Overlay1FlatVisitor = { + Root?: VisitFunctionOrObject; +}; + const legacyTypesMap = { Root: 'DefinitionRoot', ServerVariablesMap: 'ServerVariableMap', @@ -308,6 +313,13 @@ type ArazzoNestedVisitor = { : ArazzoFlatVisitor[T] & NestedVisitor; }; +type Overlay1NestedVisitor = { + // eslint-disable-next-line @typescript-eslint/ban-types + [T in keyof Overlay1FlatVisitor]: Overlay1FlatVisitor[T] extends Function + ? Overlay1FlatVisitor[T] + : Overlay1FlatVisitor[T] & NestedVisitor; +}; + export type Oas3Visitor = BaseVisitor & Oas3NestedVisitor & Record | NestedVisitObject>; @@ -328,6 +340,10 @@ export type Arazzo1Visitor = BaseVisitor & ArazzoNestedVisitor & Record | NestedVisitObject>; +export type Overlay1Visitor = BaseVisitor & + Overlay1NestedVisitor & + Record | NestedVisitObject>; + export type NestedVisitor = Exclude; export type NormalizedOasVisitors = { @@ -352,16 +368,19 @@ export type Oas2Rule = (options: Record) => Oas2Visitor | Oas2Visit export type Async2Rule = (options: Record) => Async2Visitor | Async2Visitor[]; export type Async3Rule = (options: Record) => Async3Visitor | Async3Visitor[]; export type Arazzo1Rule = (options: Record) => Arazzo1Visitor | Arazzo1Visitor[]; +export type Overlay1Rule = (options: Record) => Overlay1Visitor | Overlay1Visitor[]; export type Oas3Preprocessor = (options: Record) => Oas3Visitor; export type Oas2Preprocessor = (options: Record) => Oas2Visitor; export type Async2Preprocessor = (options: Record) => Async2Visitor; export type Async3Preprocessor = (options: Record) => Async3Visitor; export type Arazzo1Preprocessor = (options: Record) => Arazzo1Visitor; +export type Overlay1Preprocessor = (options: Record) => Overlay1Visitor; export type Oas3Decorator = (options: Record) => Oas3Visitor; export type Oas2Decorator = (options: Record) => Oas2Visitor; export type Async2Decorator = (options: Record) => Async2Visitor; export type Async3Decorator = (options: Record) => Async3Visitor; export type Arazzo1Decorator = (options: Record) => Arazzo1Visitor; +export type Overlay1Decorator = (options: Record) => Overlay1Visitor; // alias for the latest version supported // every time we update it - consider semver diff --git a/resources/overlay.yaml b/resources/overlay.yaml new file mode 100644 index 0000000000..71d3d03641 --- /dev/null +++ b/resources/overlay.yaml @@ -0,0 +1,13 @@ +overlay: 1.0.0 +info: + title: Overlay base + version: 1.0.0 +extends: 'openapi.yaml' +actions: + - target: $.info.title + description: Update title in openapi.yaml + update: + title: Overwritten by Overlay + - target: $.paths.['/things'].get.responses.['200'].content.['application/json'].schema.* + description: 'Remove the schema definition of /things' + remove: true