Skip to content

Commit c2ae47a

Browse files
committed
ADD: json schema support for frontmatter
1 parent e143d44 commit c2ae47a

File tree

8 files changed

+195
-103
lines changed

8 files changed

+195
-103
lines changed

Diff for: go.mod

+1-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ go 1.18
55
require (
66
github.com/JoshVarga/svgparser v0.0.0-20200804023048-5eaba627a7d1
77
github.com/adrg/frontmatter v0.2.0
8-
github.com/antchfx/jsonquery v1.1.5
98
github.com/antchfx/xmlquery v1.3.3
109
github.com/bmatcuk/doublestar/v4 v4.0.2
1110
github.com/cheggaaa/pb/v3 v3.0.8
@@ -16,11 +15,10 @@ require (
1615
github.com/olekukonko/tablewriter v0.0.5
1716
github.com/rogpeppe/go-internal v1.10.0
1817
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
19-
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0
18+
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
2019
github.com/shopspring/decimal v1.3.1
2120
github.com/spf13/cobra v1.7.0
2221
github.com/spf13/pflag v1.0.5
23-
github.com/xeipuuv/gojsonschema v1.2.0
2422
github.com/zyxar/image2ascii v0.0.0-20180912034614-460a04e371ae
2523
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
2624
golang.org/x/net v0.14.0
@@ -45,8 +43,6 @@ require (
4543
github.com/rivo/uniseg v0.4.2 // indirect
4644
github.com/russross/blackfriday/v2 v2.1.0 // indirect
4745
github.com/stretchr/testify v1.8.4 // indirect
48-
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
49-
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
5046
golang.org/x/sys v0.11.0 // indirect
5147
golang.org/x/text v0.12.0 // indirect
5248
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

Diff for: go.sum

+2-12
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdc
77
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
88
github.com/adrg/frontmatter v0.2.0 h1:/DgnNe82o03riBd1S+ZDjd43wAmC6W35q67NHeLkPd4=
99
github.com/adrg/frontmatter v0.2.0/go.mod h1:93rQCj3z3ZlwyxxpQioRKC1wDLto4aXHrbqIsnH9wmE=
10-
github.com/antchfx/jsonquery v1.1.5 h1:1YWrNFYCcIuJPIjFeOP5b6TXbLSUYY8qqxWbuZOB1qE=
11-
github.com/antchfx/jsonquery v1.1.5/go.mod h1:RtMzTHohKaAerkfslTNjr3Y9MdxjKlSgIgaVjVKNiug=
1210
github.com/antchfx/xmlquery v1.3.3 h1:HYmadPG0uz8CySdL68rB4DCLKXz2PurCjS3mnkVF4CQ=
1311
github.com/antchfx/xmlquery v1.3.3/go.mod h1:64w0Xesg2sTaawIdNqMB+7qaW/bSqkQm+ssPaCMWNnc=
1412
github.com/antchfx/xpath v1.1.10/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
@@ -81,8 +79,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
8179
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
8280
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
8381
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
84-
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE=
85-
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
82+
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
83+
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
8684
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
8785
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
8886
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
@@ -91,17 +89,9 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
9189
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
9290
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
9391
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
94-
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
9592
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
9693
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
9794
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
98-
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
99-
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
100-
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
101-
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
102-
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
103-
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
104-
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
10595
github.com/zyxar/image2ascii v0.0.0-20180912034614-460a04e371ae h1:EiqxsQwk1eimsz+ncJrsMMMwnkYTGiVOrLe5lGxL9cs=
10696
github.com/zyxar/image2ascii v0.0.0-20180912034614-460a04e371ae/go.mod h1:Md4Hcw0pmYWDCo1o/fHeOC2Gdhc6oDRwLim8V+SMvI0=
10797
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

Diff for: internal/command/frontmatter.go

+24-21
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,21 @@ import (
1212
)
1313

1414
var (
15-
fmStrict bool
16-
fmStrictSet map[string]bool
17-
fmSorted bool
18-
fmRequired []string
19-
fmOptional []string
20-
fmForbidden []string
21-
fmDelimiters []string
15+
fmSchemaOptions shared.SchemaOptions
16+
fmStrict bool
17+
fmStrictSet map[string]bool
18+
fmSorted bool
19+
fmRequired []string
20+
fmOptional []string
21+
fmForbidden []string
22+
fmDelimiters []string
2223
)
2324
var frontmatterCmd = &cobra.Command{
2425
Args: cobra.MinimumNArgs(1),
2526
Use: "frontmatter [options] files...",
2627
Short: "Validate frontmatter",
2728
Long: `Checks that the frontmatter in your files is valid`,
28-
PreRunE: frontmatterPrepare,
29+
PreRunE: frontmatterInit,
2930
RunE: shared.MakeFileCommand(frontmatterCheck),
3031
}
3132

@@ -39,8 +40,9 @@ func AddFrontmatterCommand(rootCmd *cobra.Command) {
3940
frontmatterCmd.Flags().BoolVar(&fmSorted, "sorted", false, "Keys need to be in alphabetical order")
4041
frontmatterCmd.Flags().StringSliceVar(&fmDelimiters, "delimiters", []string{}, "Custom delimiters (if other than `---`, `+++` and `;;;`)")
4142

43+
fmSchemaOptions.AddFlags(frontmatterCmd)
44+
4245
//LATER: report
43-
//LATER: schema
4446
}
4547

4648
func frontmatterCheck(f *shared.FileContext) {
@@ -53,7 +55,7 @@ func frontmatterCheck(f *shared.FileContext) {
5355
return
5456
}
5557

56-
yamlData := make(map[interface{}]interface{})
58+
yamlData := make(map[string]any)
5759

5860
var formats []*frontmatter.Format
5961

@@ -68,6 +70,7 @@ func frontmatterCheck(f *shared.FileContext) {
6870
}
6971
}
7072

73+
//LATER: maybe flag to require contents?
7174
_, parseErr := frontmatter.MustParse(bytes.NewReader(data), &yamlData, formats...)
7275

7376
f.RecordResult("frontmatterParse", parseErr == nil, map[string]interface{}{
@@ -97,17 +100,9 @@ func frontmatterCheck(f *shared.FileContext) {
97100

98101
if fmStrict {
99102
for key := range yamlData {
100-
keyStr, strErr := key.(string)
101-
if !strErr {
102-
f.RecordResult("frontmatterStrictParse", false, map[string]interface{}{
103-
"err": "key is not a string",
104-
"key": fmt.Sprintf("%v", key),
105-
})
106-
continue
107-
}
108-
_, ok := fmStrictSet[keyStr]
103+
_, ok := fmStrictSet[key]
109104
f.RecordResult("frontmatterStrict", ok, map[string]interface{}{
110-
"key": keyStr,
105+
"key": key,
111106
})
112107
}
113108
}
@@ -133,9 +128,11 @@ func frontmatterCheck(f *shared.FileContext) {
133128
previousKey = currentKey
134129
}
135130
}
131+
132+
fmSchemaOptions.Validate(f, yamlData)
136133
}
137134

138-
func frontmatterPrepare(cmd *cobra.Command, args []string) error {
135+
func frontmatterInit(cmd *cobra.Command, args []string) error {
139136
if fmStrict {
140137
fmStrictSet = make(map[string]bool)
141138
for _, key := range fmRequired {
@@ -150,5 +147,11 @@ func frontmatterPrepare(cmd *cobra.Command, args []string) error {
150147
fmt.Fprintf(os.Stderr, "ERROR: delimiter count must be <=2 (passed %d)", len(fmDelimiters))
151148
os.Exit(7)
152149
}
150+
151+
schemaPrepErr := fmSchemaOptions.Prepare()
152+
if schemaPrepErr != nil {
153+
return schemaPrepErr
154+
}
155+
153156
return nil
154157
}

Diff for: internal/command/json.go

+15-56
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
package command
22

33
import (
4-
"bytes"
5-
"fmt"
6-
"net/url"
7-
"os"
8-
"path/filepath"
4+
"encoding/json"
95

106
"github.com/FileFormatInfo/fflint/internal/shared"
11-
"github.com/antchfx/jsonquery"
7+
//"github.com/antchfx/jsonquery"
128
"github.com/spf13/cobra"
13-
"github.com/xeipuuv/gojsonschema"
149
)
1510

1611
var (
17-
jsonSchemaLocation string
18-
jsonSchema *gojsonschema.Schema
12+
jsonSchemaValidator shared.SchemaOptions
13+
jsonSchemaLocation string
1914
)
2015

2116
// jsonCmd represents the json command
@@ -31,10 +26,8 @@ var jsonCmd = &cobra.Command{
3126
func AddJsonCommand(rootCmd *cobra.Command) {
3227
rootCmd.AddCommand(jsonCmd)
3328

34-
jsonCmd.Flags().StringVar(&jsonSchemaLocation, "schema", "", "JSON Schema to validate against") //LATER: link to docs about embedded ones
35-
29+
jsonSchemaValidator.AddFlags(jsonCmd)
3630
//LATER: whitespace: canonical/none/any
37-
//LATER: schema (https://github.com/xeipuuv/gojsonschema)
3831
}
3932

4033
func jsonCheck(f *shared.FileContext) {
@@ -47,59 +40,25 @@ func jsonCheck(f *shared.FileContext) {
4740
return
4841
}
4942

50-
_, parseErr := jsonquery.Parse(bytes.NewReader(data))
43+
var jsonData any
44+
parseErr := json.Unmarshal(data, &jsonData)
5145

46+
f.RecordResult("jsonParse", parseErr == nil, map[string]interface{}{
47+
"error": parseErr,
48+
})
5249
if parseErr != nil {
53-
f.RecordResult("jsonParse", false, map[string]interface{}{
54-
"error": parseErr,
55-
})
5650
return
5751
}
5852

59-
if jsonSchema != nil {
60-
result, validateErr := jsonSchema.Validate(gojsonschema.NewStringLoader(string(data)))
61-
if validateErr != nil {
62-
f.RecordResult("jsonSchemaRun", false, map[string]interface{}{
63-
"error": validateErr.Error(),
64-
})
65-
} else {
66-
f.RecordResult("jsonSchemaValidate", result.Valid(), map[string]interface{}{
67-
"errors": result.Errors(),
68-
})
69-
}
70-
}
71-
53+
jsonSchemaValidator.Validate(f, jsonData)
7254
}
7355

7456
func jsonInit(cmd *cobra.Command, args []string) error {
7557

76-
if jsonSchemaLocation == "" {
77-
return nil
78-
}
79-
80-
// work with local file urls
81-
jsonUrl, urlParseErr := url.Parse(jsonSchemaLocation)
82-
if urlParseErr != nil {
83-
return urlParseErr
84-
}
85-
86-
// allow relative local file schemas
87-
if jsonUrl.Scheme == "" {
88-
jsonUrl.Scheme = "file"
89-
jsonPath, pathErr := filepath.Abs(jsonUrl.Path)
90-
if pathErr != nil {
91-
return pathErr
92-
}
93-
jsonUrl.Path = jsonPath
94-
newLocation := jsonUrl.String()
95-
if shared.Debug {
96-
fmt.Fprintf(os.Stderr, "DEBUG: canonicalizing schema path from '%s' to '%s'\n", jsonSchemaLocation, newLocation)
97-
}
98-
jsonSchemaLocation = newLocation
58+
prepErr := jsonSchemaValidator.Prepare()
59+
if prepErr != nil {
60+
return prepErr
9961
}
10062

101-
jsonSchemaLoader := gojsonschema.NewReferenceLoader(jsonSchemaLocation)
102-
var schemaErr error
103-
jsonSchema, schemaErr = gojsonschema.NewSchema(jsonSchemaLoader)
104-
return schemaErr
63+
return nil
10564
}

Diff for: internal/shared/schema.go

+29-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package shared
22

33
import (
4+
"fmt"
5+
"os"
6+
47
"github.com/santhosh-tekuri/jsonschema/v5"
58
"github.com/spf13/cobra"
69
)
@@ -11,20 +14,43 @@ type SchemaOptions struct {
1114
}
1215

1316
func (so *SchemaOptions) AddFlags(theCmd *cobra.Command) {
14-
1517
theCmd.Flags().StringVar(&so.src, "schema", "", "JSON schema path (or URL)")
1618
}
1719

1820
func (so *SchemaOptions) Prepare() error {
21+
if Debug {
22+
fmt.Fprintf(os.Stderr, "DEBUG: trying to compile schema: %s\n", so.src)
23+
}
24+
if so.src == "" {
25+
return nil
26+
}
1927
compiled, err := jsonschema.Compile(so.src)
2028
if err != nil {
29+
if Debug {
30+
fmt.Fprintf(os.Stderr, "DEBUG: schema compilation error: %v\n", err)
31+
}
2132
return err
2233
}
2334

35+
if Debug {
36+
fmt.Fprintf(os.Stderr, "DEBUG: successfully compiled schema from %s\n", so.src)
37+
}
38+
2439
so.compiled = compiled
2540
return nil
2641
}
2742

28-
func (so *SchemaOptions) Validate(data interface{}) error {
29-
return so.compiled.Validate(data)
43+
func (so *SchemaOptions) Validate(f *FileContext, data any) error {
44+
if so.compiled == nil {
45+
return nil
46+
}
47+
validationErr := so.compiled.Validate(data)
48+
if Debug {
49+
fmt.Fprintf(os.Stderr, "DEBUG: schema validation error: %v\n", validationErr)
50+
}
51+
f.RecordResult("jsonSchemaValidatation", validationErr == nil, map[string]interface{}{
52+
"error": validationErr,
53+
})
54+
55+
return validationErr
3056
}

Diff for: run.sh

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,29 @@
11
#!/usr/bin/env bash
22
#
3-
# run locally
3+
# run tests locally
44
#
55

66
set -o errexit
77
set -o pipefail
88
set -o nounset
99

10-
rm -rf ./fflint
10+
if [ -f "./fflint" ]; then
11+
echo "INFO: removing old build of fflint"
12+
rm ./fflint
13+
fi
14+
15+
echo "INFO: building new fflint"
1116
go build -o ./fflint cmd/fflint/main.go
12-
export PATH=$PATH:$(pwd)
13-
fflint version
14-
go test -timeout 30s -run "^TestFflint$" github.com/FileFormatInfo/fflint/cmd/fflint
17+
if [ ! -f "./fflint" ]; then
18+
echo "ERROR: failed to build fflint"
19+
exit 1
20+
fi
21+
22+
23+
export PATH=$(pwd):$PATH
24+
echo "INFO: running fflint version $(fflint version)"
25+
26+
echo "INFO: running tests"
27+
go test -timeout 30s -run "^TestFflint$" github.com/FileFormatInfo/fflint/cmd/fflint
28+
29+
echo "INFO: complete at $(date -u +%Y-%m-%dT%H:%M:%SZ)"

0 commit comments

Comments
 (0)