I've just put websockets instead of websocket and deploy was successful, altough no Websocket API and no binding was setup.
It'll be great if such issues are picked and communicated by the framework, Still it should be done with respect to plugins ,which may support events that are not supported at Framework Core.
Ideally we should have fine grain validation towards serverless.yml (backed with library as @hapi/joi), which can be extended by the plugins.
With v1 we can introduce a non-breaking version that'll just show warnings, while with next major it can throw meaningful errors.
Validation should be made after all variables are resolved.
As introducing complete validation for whole config as we support now seems non-trivial and very time consuming task. It'll be good to start with something minimal that provides basic validation and just allows additional properties in places where schema is not complete.
It'll allow us to deliver early, and then improve and fine tune schema on the go, and cover new functionalities with complete schema upfront.
Where validation logic should be attached?
Validation should not be configured as an internal plugin but be a part of a core (so be incorporated into engine which is also responsible for load of config, resolving variables, initializing plugins). It should be considered as a core feature.
When validation should happen?
Ideally validation should happen after all plugins were initialized and variables resolved but before life-cycle engine jump in.
Technically it's when current service.validate() happens:
|
validate() { |
|
_.forEach(this.functions, (functionObj, functionName) => { |
|
if (!_.isArray(functionObj.events)) { |
|
throw new ServerlessError( |
|
`Events for "${functionName}" must be an array, not an ${typeof functionObj.events}` |
|
); |
|
} |
|
}); |
|
|
|
const provider = this.serverless.getProvider('aws'); |
|
if (provider) { |
|
const stage = provider.getStage(); |
|
this.getAllFunctions().forEach(funcName => { |
|
_.forEach(this.getAllEventsInFunction(funcName), event => { |
|
if (_.has(event, 'http') && !validAPIGatewayStageNamePattern.test(stage)) { |
|
throw new this.serverless.classes.Error( |
|
[ |
|
`Invalid stage name ${stage}:`, |
|
'it should contains only [-_a-zA-Z0-9] for AWS provider if http event are present', |
|
'according to API Gateway limitation.', |
|
].join(' ') |
|
); |
|
} |
|
}); |
|
}); |
|
} |
|
|
|
return this; |
|
} |
Which already tries to do some validation which should we replace and back with new schema validator
What objects should validation cover?
Some problem is that current config handling is not clean. We do not have one object with which we work, but we span config parts on our service instance here:
|
if (_.isObject(serverlessFile.service)) { |
|
that.serviceObject = serverlessFile.service; |
|
that.service = serverlessFile.service.name; |
|
} else { |
|
that.serviceObject = { name: serverlessFile.service }; |
|
that.service = serverlessFile.service; |
|
} |
|
|
|
that.app = serverlessFile.app; |
|
that.tenant = serverlessFile.tenant; |
|
that.org = serverlessFile.org; |
|
that.custom = serverlessFile.custom; |
|
that.plugins = serverlessFile.plugins; |
|
that.resources = serverlessFile.resources; |
|
that.functions = serverlessFile.functions || {}; |
|
|
|
// merge so that the default settings are still in place and |
|
// won't be overwritten |
|
that.provider = _.merge(that.provider, serverlessFile.provider); |
|
|
|
if (serverlessFile.package) { |
|
that.package.individually = serverlessFile.package.individually; |
|
that.package.path = serverlessFile.package.path; |
|
that.package.artifact = serverlessFile.package.artifact; |
|
that.package.exclude = serverlessFile.package.exclude; |
|
that.package.include = serverlessFile.package.include; |
|
that.package.excludeDevDependencies = serverlessFile.package.excludeDevDependencies; |
|
} |
|
|
|
if (that.provider.name === 'aws') { |
|
that.layers = serverlessFile.layers || {}; |
|
} |
|
|
|
that.outputs = serverlessFile.outputs; |
and then work with that. Variables resolution is made on service instance.
Ideally after Variables resolution is made. I think we should prepare a plain object that derives from service as follows:
const configToValidate = {
service: service.serviceObject,
app: service.app,
org: service.org,
custom: service.custom,
plugins: service.plugins,
resources: service.resources,
functions: service.functions.
provider: service.provider,
package: service.package,
layers: service.layers,
outputs: service.outputs
}
And validate that.
What validation library should we use?
In consideration:
We need a validation tool that allows to easily in generic way extend base schema (e.g. define a new property on some object which is a deep nested property in global schema).
So we don't have to upfront define those eventual extension points, as otherwise we can easily run into maintenance issues in a future (it's likely that we will need more and more customization options on the way)
I'm not that familiar with@hapi/joi and I'm not sure if it provides easy means for that (by first look, it appears to me it doesn't, but I might have missed something)
We may also consider: https://github.com/epoberezkin/ajv which is based on JSON schema standard (so schema on it's own is a JSON, therefore extensible). It also supports $merge instructions dedicated for that https://github.com/epoberezkin/ajv#merge-and-patch-keywords
Before we jump into implementation, let's prepare a working a example, where we have some example schema with deep nested objects, and then we extend some deep objects with schema for additional properties.
Having that we could be more confident that we've chosen right schema validator.
I've just put
websocketsinstead ofwebsocketand deploy was successful, altough no Websocket API and no binding was setup.It'll be great if such issues are picked and communicated by the framework, Still it should be done with respect to plugins ,which may support events that are not supported at Framework Core.
Ideally we should have fine grain validation towards
serverless.yml(backed with library as @hapi/joi), which can be extended by the plugins.With v1 we can introduce a non-breaking version that'll just show warnings, while with next major it can throw meaningful errors.
Validation should be made after all variables are resolved.
As introducing complete validation for whole config as we support now seems non-trivial and very time consuming task. It'll be good to start with something minimal that provides basic validation and just allows additional properties in places where schema is not complete.
It'll allow us to deliver early, and then improve and fine tune schema on the go, and cover new functionalities with complete schema upfront.
Where validation logic should be attached?
Validation should not be configured as an internal plugin but be a part of a core (so be incorporated into engine which is also responsible for load of config, resolving variables, initializing plugins). It should be considered as a core feature.
When validation should happen?
Ideally validation should happen after all plugins were initialized and variables resolved but before life-cycle engine jump in.
Technically it's when current
service.validate()happens:serverless/lib/classes/Service.js
Lines 174 to 202 in 7abb23e
Which already tries to do some validation which should we replace and back with new schema validator
What objects should validation cover?
Some problem is that current config handling is not clean. We do not have one object with which we work, but we span config parts on our service instance here:
serverless/lib/classes/Service.js
Lines 98 to 131 in 7abb23e
and then work with that. Variables resolution is made on
serviceinstance.Ideally after Variables resolution is made. I think we should prepare a plain object that derives from service as follows:
And validate that.
What validation library should we use?
In consideration:
We need a validation tool that allows to easily in generic way extend base schema (e.g. define a new property on some object which is a deep nested property in global schema).
So we don't have to upfront define those eventual extension points, as otherwise we can easily run into maintenance issues in a future (it's likely that we will need more and more customization options on the way)
I'm not that familiar with
@hapi/joiand I'm not sure if it provides easy means for that (by first look, it appears to me it doesn't, but I might have missed something)We may also consider: https://github.com/epoberezkin/ajv which is based on JSON schema standard (so schema on it's own is a JSON, therefore extensible). It also supports
$mergeinstructions dedicated for that https://github.com/epoberezkin/ajv#merge-and-patch-keywordsBefore we jump into implementation, let's prepare a working a example, where we have some example schema with deep nested objects, and then we extend some deep objects with schema for additional properties.
Having that we could be more confident that we've chosen right schema validator.