diff --git a/lib/classes/ConfigSchemaHandler/index.js b/lib/classes/ConfigSchemaHandler/index.js index 3cabca001dd..b42925cc7a4 100644 --- a/lib/classes/ConfigSchemaHandler/index.js +++ b/lib/classes/ConfigSchemaHandler/index.js @@ -25,24 +25,21 @@ const normalizeSchemaObject = (object, instanceSchema) => { // Normalizer is introduced to workaround https://github.com/ajv-validator/ajv/issues/1287 // normalizedObjectsMap allows to handle circular structures without issues -const normalizedObjectsMap = new WeakMap(); +const normalizedObjectsSet = new WeakSet(); const normalizeUserConfig = object => { - if (normalizedObjectsMap.has(object)) return normalizedObjectsMap.get(object); + if (normalizedObjectsSet.has(object)) return object; + normalizedObjectsSet.add(object); if (Array.isArray(object)) { - const normalizedObject = []; - normalizedObjectsMap.set(object, normalizedObject); for (const value of object) { - normalizedObject.push(_.isObject(value) ? normalizeUserConfig(value) : value); + if (_.isObject(value)) normalizeUserConfig(value); + } + } else { + for (const [key, value] of Object.entries(object)) { + if (value == null) delete object[key]; + else if (_.isObject(value)) normalizeUserConfig(value); } - return normalizedObject; - } - const normalizedObject = Object.create(null); - normalizedObjectsMap.set(object, normalizedObject); - for (const [key, value] of Object.entries(object)) { - if (value == null) continue; - normalizedObject[key] = _.isObject(value) ? normalizeUserConfig(value) : value; } - return normalizedObject; + return object; }; class ConfigSchemaHandler { @@ -94,7 +91,7 @@ class ConfigSchemaHandler { this.relaxProviderSchema(); } - const ajv = new Ajv({ allErrors: true, verbose: true }); + const ajv = new Ajv({ allErrors: true, coerceTypes: 'array', verbose: true }); require('ajv-keywords')(ajv, 'regexp'); // Workaround https://github.com/ajv-validator/ajv/issues/1255 normalizeSchemaObject(this.schema, this.schema); @@ -210,13 +207,15 @@ class ConfigSchemaHandler { } relaxProviderSchema() { + // provider this.schema.properties.provider.additionalProperties = true; + + // functions[] this.schema.properties.functions.patternProperties[ FUNCTION_NAME_PATTERN ].additionalProperties = true; - // Do not report errors regarding unsupported function events as - // their schemas are not defined. + // functions[].events[] if ( Array.isArray( this.schema.properties.functions.patternProperties[FUNCTION_NAME_PATTERN].properties.events diff --git a/lib/configSchema.js b/lib/configSchema.js index f035a16f011..fd063bc1bdf 100644 --- a/lib/configSchema.js +++ b/lib/configSchema.js @@ -17,7 +17,6 @@ const schema = { disabledDeprecations: { anyOf: [ { const: '*' }, - { $ref: '#/definitions/errorCode' }, { type: 'array', items: { $ref: '#/definitions/errorCode' }, diff --git a/lib/plugins/aws/package/compile/events/alb/index.js b/lib/plugins/aws/package/compile/events/alb/index.js index ebf3be3e69f..dbcc45dae30 100644 --- a/lib/plugins/aws/package/compile/events/alb/index.js +++ b/lib/plugins/aws/package/compile/events/alb/index.js @@ -7,10 +7,8 @@ const compileTargetGroups = require('./lib/targetGroups'); const compileListenerRules = require('./lib/listenerRules'); const compilePermissions = require('./lib/permissions'); -function arrayOrSingleSchema(schema) { - return { - oneOf: [schema, { type: 'array', items: schema }], - }; +function defineArray(schema) { + return { type: 'array', items: schema }; } class AwsCompileAlbEvents { @@ -37,7 +35,7 @@ class AwsCompileAlbEvents { this.serverless.configSchemaHandler.defineFunctionEvent('aws', 'alb', { type: 'object', properties: { - authorizer: arrayOrSingleSchema({ type: 'string' }), + authorizer: defineArray({ type: 'string' }), conditions: { type: 'object', properties: { @@ -50,19 +48,19 @@ class AwsCompileAlbEvents { additionalProperties: false, required: ['name', 'values'], }, - host: arrayOrSingleSchema({ + host: defineArray({ type: 'string', pattern: '^[A-Za-z0-9*?.-]+$', maxLength: 128, }), - ip: arrayOrSingleSchema({ + ip: defineArray({ oneOf: [ { type: 'string', format: 'ipv4' }, { type: 'string', format: 'ipv6' }, ], }), - method: arrayOrSingleSchema({ type: 'string', pattern: '^[A-Z_-]+$', maxLength: 40 }), - path: arrayOrSingleSchema({ + method: defineArray({ type: 'string', pattern: '^[A-Z_-]+$', maxLength: 40 }), + path: defineArray({ type: 'string', pattern: '^([A-Za-z0-9*?_.$/~"\'@:+-]|&)+$', maxLength: 128, diff --git a/lib/plugins/aws/package/compile/events/httpApi/index.js b/lib/plugins/aws/package/compile/events/httpApi/index.js index 150f362a218..e065387526a 100644 --- a/lib/plugins/aws/package/compile/events/httpApi/index.js +++ b/lib/plugins/aws/package/compile/events/httpApi/index.js @@ -68,9 +68,7 @@ class HttpApiEvents { anyOf: [{ type: 'string' }, { $ref: '#/definitions/awsCfFunction' }], }, name: { type: 'string' }, - scopes: { - oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], - }, + scopes: { type: 'array', items: { type: 'string' } }, }, oneOf: [{ required: ['id'] }, { required: ['name'] }], additionalProperties: false, diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index 2eb481ea271..8e3b7d636f5 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -282,9 +282,7 @@ class AwsProvider { required: ['Fn::Sub'], additionalProperties: false, }, - awsIamPolicyAction: { - anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], - }, + awsIamPolicyAction: { type: 'array', items: { type: 'string' } }, awsIamPolicyPrincipal: { anyOf: [ { const: '*' }, @@ -294,30 +292,19 @@ class AwsProvider { AWS: { anyOf: [ { const: '*' }, - { $ref: '#/definitions/awsArn' }, { type: 'array', items: { $ref: '#/definitions/awsArn' } }, ], }, - Federated: { - anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], - }, - Service: { - anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], - }, - CanonicalUser: { - anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], - }, + Federated: { type: 'array', items: { type: 'string' } }, + Service: { type: 'array', items: { type: 'string' } }, + CanonicalUser: { type: 'array', items: { type: 'string' } }, }, additionalProperties: false, }, ], }, awsIamPolicyResource: { - anyOf: [ - { const: '*' }, - { $ref: '#/definitions/awsArn' }, - { type: 'array', items: { $ref: '#/definitions/awsArn' } }, - ], + anyOf: [{ const: '*' }, { type: 'array', items: { $ref: '#/definitions/awsArn' } }], }, // Definition of Statement taken from https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_grammar.html#policies-grammar-bnf awsIamPolicyStatements: { @@ -405,9 +392,7 @@ class AwsProvider { pattern: '^[/#A-Za-z0-9-_.]+$', }, awsResourceCondition: { type: 'string' }, - awsResourceDependsOn: { - oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], - }, + awsResourceDependsOn: { type: 'array', items: { type: 'string' } }, awsResourceProperties: { Properties: { type: 'object' }, CreationPolicy: { type: 'object' }, @@ -489,14 +474,8 @@ class AwsProvider { identitySource: { $ref: '#/definitions/awsCfInstruction' }, issuerUrl: { $ref: '#/definitions/awsCfInstruction' }, audience: { - oneOf: [ - { $ref: '#/definitions/awsCfInstruction' }, - { - type: 'array', - items: { $ref: '#/definitions/awsCfInstruction' }, - additionalItems: false, - }, - ], + type: 'array', + items: { $ref: '#/definitions/awsCfInstruction' }, }, }, required: ['identitySource', 'issuerUrl', 'audience'], @@ -510,18 +489,10 @@ class AwsProvider { type: 'object', properties: { allowCredentials: { type: 'boolean' }, - allowedHeaders: { - oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], - }, - allowedMethods: { - oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], - }, - allowedOrigins: { - oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], - }, - exposedResponseHeaders: { - oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], - }, + allowedHeaders: { type: 'array', items: { type: 'string' } }, + allowedMethods: { type: 'array', items: { type: 'string' } }, + allowedOrigins: { type: 'array', items: { type: 'string' } }, + exposedResponseHeaders: { type: 'array', items: { type: 'string' } }, maxAge: { type: 'integer', minimum: 0 }, }, additionalProperties: false,