The vacuum language server is a Language Server Protocol (LSP)
compatible server that can be used with your favorite editor to lint OpenAPI specifications in real time.
vacuum added support for the LSP in v0.9.0 and it can be used via the language-server command
This is great, as long as you only need to use vacuum’s built in ruleset.
The majority of the same flags that are available to the lint command are also available to the language-server command.
The only exception is all visual options won’t be available, as the LSP is designed to be used in a text editor.
Configuring the language server
The vacuum language server can be configured using a vacuum.conf.yaml file. Read more about configuring vacuum.
Dynamically loading rulesets based on file content
This functionality only applies if you are using vacuum as a library to implement your own custom language server.
Scenario: let’s say you have numerous OpenAPI specs at various quality levels, and you’re trying to bring everything up to your new required standards. Pragmatically you might want to use a smaller set of rules for existing specs so you (or the teams you’re working with) don’t have to spend huge amounts of time getting existing things up to current standards, and stricter rules for newer specs to make sure everything new you’re building adheres to your new standards.
You can easily apply whatever linting rules you need when linting the spec, by parsing the specification before
running the actual linting, but when using a language server you don’t get the document content until the document gets
opened, that’s where NewServerWithRulesetSelector comes in.
You can define a RulesetSelector function, which takes in the DocumentContext and returns the *rulesets.RuleSet
relevant for that document.
So lets say, I want to enable the vacuum default rules for all specs, but have some OpenAPI specs that need additional
security checks, so want to enable the OWASP ruleset for only certain specs.
I could ask teams to remember what ruleset their specs should be using, or I could create a RulesetSelector
function that makes that choice for them, and configures everything automatically.
Example
- Create a function that returns the
RulesetSelectorfunction.
This example reads the document, and extracts the serviceName value specified in the x-domain-gateway-integration
node, but in practice it can extract whatever information you need to act off.
import (
"slices"
languageserver "github.com/daveshanley/vacuum/language-server"
"github.com/daveshanley/vacuum/rulesets"
"go.yaml.in/yaml/v4"
)
func SelectRulesets() languageserver.RulesetSelector {
return func(doc *languageserver.DocumentContext) *rulesets.RuleSet {
secureServices := []string{
"service-one",
"service-three",
}
if slices.Contains(secureServices, extractServiceName(doc.Content)) {
return rulesets.GenerateOWASPOpenAPIRuleSet()
}
return rulesets.GenerateDefaultOpenAPIRuleSet()
}
}
func extractServiceName(specBytes []byte) string {
var spec struct {
Info struct {
XDomainGatewayIntegration struct {
ServiceName string `yaml:"serviceName"`
} `yaml:"x-domain-gateway-integration"`
} `yaml:"info"`
}
if err := yaml.Unmarshal(specBytes, &spec); err != nil {
return ""
}
return spec.Info.XDomainGatewayIntegration.ServiceName
}
- Pass the selector function to
NewServerWithRulesetSelector.
Where you’re starting the language server you can then pass in your selector function like so:
func Start(version string, lintConfig utils.LintFileRequest, selector languageserver.RulesetSelector) {
languageserver.NewServerWithRulesetSelector(
version,
&lintConfig,
SelectRulesets(),
).Run()
}
Now when you open service-one or service-three’s OpenAPI specs in your editor with the language server
running you will get feedback from the OWASP ruleset, but when you open any other OpenAPI spec you will
get the default rules.
The dynamically loading rulesets based on file content functionality was a community contribution from tx3stn
