Skip to content

Commit df552db

Browse files
reversearrowdaveshanley
authored andcommitted
feat: add cli cmd to validate single oas spec
1 parent 86b8d0e commit df552db

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed

README.md

+23
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,29 @@ A validation module for [libopenapi](https://github.com/pb33f/libopenapi).
2828
go get github.com/pb33f/libopenapi-validator
2929
```
3030

31+
## Validate OpenAPI Document
32+
33+
```bash
34+
go run github.com/pb33f/libopenapi-validator/cmd/validate@latest [--regexengine] <file>
35+
```
36+
🔍 Example: Use a custom regex engine/flag (e.g., ecmascript)
37+
```bash
38+
go run github.com/pb33f/libopenapi-validator/cmd/validate@latest --regexengine=ecmascript <file>
39+
```
40+
🔧 Supported **--regexengine** flags/values (ℹ️ Default: re2)
41+
- none
42+
- ignorecase
43+
- multiline
44+
- explicitcapture
45+
- compiled
46+
- singleline
47+
- ignorepatternwhitespace
48+
- righttoleft
49+
- debug
50+
- ecmascript
51+
- re2
52+
- unicode
53+
3154
## Documentation
3255

3356
- [The structure of the validator](https://pb33f.io/libopenapi/validation/#the-structure-of-the-validator)

cmd/validate/main.go

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"flag"
6+
"fmt"
7+
"log/slog"
8+
"os"
9+
10+
"github.com/dlclark/regexp2"
11+
"github.com/pb33f/libopenapi"
12+
"github.com/santhosh-tekuri/jsonschema/v6"
13+
14+
validator "github.com/pb33f/libopenapi-validator"
15+
"github.com/pb33f/libopenapi-validator/config"
16+
)
17+
18+
type customRegexp regexp2.Regexp
19+
20+
func (re *customRegexp) MatchString(s string) bool {
21+
matched, err := (*regexp2.Regexp)(re).MatchString(s)
22+
return err == nil && matched
23+
}
24+
25+
func (re *customRegexp) String() string {
26+
return (*regexp2.Regexp)(re).String()
27+
}
28+
29+
type regexEngine struct {
30+
runtimeOption regexp2.RegexOptions
31+
}
32+
33+
func (e *regexEngine) run(s string) (jsonschema.Regexp, error) {
34+
re, err := regexp2.Compile(s, e.runtimeOption)
35+
if err != nil {
36+
return nil, err
37+
}
38+
return (*customRegexp)(re), nil
39+
}
40+
41+
var regexParsingOptionsMap = map[string]regexp2.RegexOptions{
42+
"none": regexp2.None,
43+
"ignorecase": regexp2.IgnoreCase,
44+
"multiline": regexp2.Multiline,
45+
"explicitcapture": regexp2.ExplicitCapture,
46+
"compiled": regexp2.Compiled,
47+
"singleline": regexp2.Singleline,
48+
"ignorepatternwhitespace": regexp2.IgnorePatternWhitespace,
49+
"righttoleft": regexp2.RightToLeft,
50+
"debug": regexp2.Debug,
51+
"ecmascript": regexp2.ECMAScript,
52+
"re2": regexp2.RE2,
53+
"unicode": regexp2.Unicode,
54+
}
55+
56+
var (
57+
defaultRegexEngine = ""
58+
regexParsingOptions = flag.String("regexengine", defaultRegexEngine, `Specify the regex parsing option to use.
59+
Supported values are:
60+
Engines: re2 (default), ecmascript
61+
Flags: ignorecase, multiline, explicitcapture, compiled,
62+
singleline, ignorepatternwhitespace, righttoleft,
63+
debug, unicode
64+
If not specified, the default libopenapi option is "re2".
65+
66+
If not specified, the default libopenapi regex engine is "re2"".`)
67+
)
68+
69+
func main() {
70+
flag.Usage = func() {
71+
fmt.Fprintf(os.Stderr, `Usage: validate [OPTIONS] <file>
72+
73+
Validates an OpenAPI document using libopenapi-validator.
74+
75+
Options:
76+
--regexengine string Specify the regex parsing option to use.
77+
Supported values are:
78+
Engines: re2 (default), ecmascript
79+
Flags: ignorecase, multiline, explicitcapture, compiled,
80+
singleline, ignorepatternwhitespace, righttoleft,
81+
debug, unicode
82+
If not specified, the default libopenapi option is "re2".
83+
84+
-h, --help Show this help message and exit.
85+
`)
86+
}
87+
88+
for _, arg := range os.Args[1:] {
89+
if arg == "--help" || arg == "-h" {
90+
flag.Usage()
91+
os.Exit(0)
92+
}
93+
}
94+
95+
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
96+
flag.Parse()
97+
filename := flag.Arg(0)
98+
if len(flag.Args()) != 1 || filename == "" {
99+
logger.Error("missing file argument", slog.Any("args", os.Args))
100+
flag.Usage()
101+
os.Exit(1)
102+
}
103+
validationOpts := []config.Option{}
104+
if *regexParsingOptions != "" {
105+
regexEngineOption, ok := regexParsingOptionsMap[*regexParsingOptions]
106+
if !ok {
107+
logger.Error("unsupported regex option provided",
108+
slog.String("provided", *regexParsingOptions),
109+
slog.Any("supported", []string{
110+
"none",
111+
"ignorecase",
112+
"multiline",
113+
"explicitcapture",
114+
"compiled",
115+
"singleline",
116+
"ignorepatternwhitespace",
117+
"righttoleft",
118+
"debug",
119+
"ecmascript",
120+
"re2",
121+
"unicode",
122+
}),
123+
)
124+
os.Exit(1)
125+
}
126+
reEngine := &regexEngine{
127+
runtimeOption: regexEngineOption,
128+
}
129+
130+
validationOpts = append(validationOpts, config.WithRegexEngine(reEngine.run))
131+
}
132+
133+
data, err := os.ReadFile(filename)
134+
if err != nil {
135+
logger.Error("error reading file", slog.String("provided", filename), slog.Any("error", err))
136+
os.Exit(1)
137+
}
138+
139+
doc, err := libopenapi.NewDocument(data)
140+
if err != nil {
141+
logger.Error("error creating new libopenapi document", slog.Any("error", err))
142+
os.Exit(1)
143+
}
144+
145+
docValidator, validatorErrs := validator.NewValidator(doc, validationOpts...)
146+
if len(validatorErrs) > 0 {
147+
logger.Error("error creating a new validator", slog.Any("errors", errors.Join(validatorErrs...)))
148+
os.Exit(1)
149+
}
150+
151+
valid, validationErrs := docValidator.ValidateDocument()
152+
if !valid {
153+
logger.Error("validation errors", slog.Any("errors", validationErrs))
154+
os.Exit(1)
155+
}
156+
logger.Info("document passes all validations", slog.String("filename", filename))
157+
}

0 commit comments

Comments
 (0)