Skip to content

Commit bf73f9e

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

File tree

8 files changed

+290
-119
lines changed

8 files changed

+290
-119
lines changed

Diff for: go.mod

+2-6
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,15 +15,13 @@ 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
27-
gopkg.in/yaml.v2 v2.4.0
2825
gopkg.in/yaml.v3 v3.0.1
2926
)
3027

@@ -45,9 +42,8 @@ require (
4542
github.com/rivo/uniseg v0.4.2 // indirect
4643
github.com/russross/blackfriday/v2 v2.1.0 // indirect
4744
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
5045
golang.org/x/sys v0.11.0 // indirect
5146
golang.org/x/text v0.12.0 // indirect
5247
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
48+
gopkg.in/yaml.v2 v2.4.0 // indirect
5349
)

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

+59-36
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,25 @@ import (
88
"github.com/FileFormatInfo/fflint/internal/shared"
99
"github.com/adrg/frontmatter"
1010
"github.com/spf13/cobra"
11-
"gopkg.in/yaml.v2"
11+
"gopkg.in/yaml.v3"
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,8 +55,6 @@ func frontmatterCheck(f *shared.FileContext) {
5355
return
5456
}
5557

56-
yamlData := make(map[interface{}]interface{})
57-
5858
var formats []*frontmatter.Format
5959

6060
if len(fmDelimiters) > 0 {
@@ -68,7 +68,9 @@ func frontmatterCheck(f *shared.FileContext) {
6868
}
6969
}
7070

71-
_, parseErr := frontmatter.MustParse(bytes.NewReader(data), &yamlData, formats...)
71+
yamlRawData := make(map[string]any)
72+
//LATER: maybe flag to require contents?
73+
_, parseErr := frontmatter.MustParse(bytes.NewReader(data), &yamlRawData, formats...)
7274

7375
f.RecordResult("frontmatterParse", parseErr == nil, map[string]interface{}{
7476
"error": shared.ErrString(parseErr),
@@ -77,6 +79,15 @@ func frontmatterCheck(f *shared.FileContext) {
7779
return
7880
}
7981

82+
/*
83+
yamlDataOrArray, stringKeysErr := shared.ToStringKeys(yamlRawData)
84+
f.RecordResult("frontmatterStringKeys", stringKeysErr == nil, map[string]interface{}{
85+
"error": stringKeysErr,
86+
})
87+
*/
88+
yamlDataOrArray := convert(yamlRawData)
89+
yamlData := yamlDataOrArray.(map[string]interface{})
90+
8091
if len(fmRequired) > 0 {
8192
for _, key := range fmRequired {
8293
_, ok := yamlData[key]
@@ -97,45 +108,28 @@ func frontmatterCheck(f *shared.FileContext) {
97108

98109
if fmStrict {
99110
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]
111+
_, ok := fmStrictSet[key]
109112
f.RecordResult("frontmatterStrict", ok, map[string]interface{}{
110-
"key": keyStr,
113+
"key": key,
111114
})
112115
}
113116
}
114117

115118
if fmSorted {
116-
sortedData := yaml.MapSlice{}
117-
118-
frontmatter.Parse(bytes.NewReader(data), &sortedData)
119119
previousKey := ""
120-
for _, item := range sortedData {
121-
currentKey, strErr := item.Key.(string)
122-
if !strErr {
123-
f.RecordResult("frontmatterSortedParse", false, map[string]interface{}{
124-
"err": "key is not a string",
125-
"key": fmt.Sprintf("%v", item.Key),
126-
})
127-
continue
128-
}
120+
for currentKey := range yamlData {
129121
f.RecordResult("frontmatterSorted", previousKey < currentKey, map[string]interface{}{
130122
"previous": previousKey,
131123
"current": currentKey,
132124
})
133125
previousKey = currentKey
134126
}
135127
}
128+
129+
fmSchemaOptions.Validate(f, yamlData)
136130
}
137131

138-
func frontmatterPrepare(cmd *cobra.Command, args []string) error {
132+
func frontmatterInit(cmd *cobra.Command, args []string) error {
139133
if fmStrict {
140134
fmStrictSet = make(map[string]bool)
141135
for _, key := range fmRequired {
@@ -150,5 +144,34 @@ func frontmatterPrepare(cmd *cobra.Command, args []string) error {
150144
fmt.Fprintf(os.Stderr, "ERROR: delimiter count must be <=2 (passed %d)", len(fmDelimiters))
151145
os.Exit(7)
152146
}
147+
148+
schemaPrepErr := fmSchemaOptions.Prepare()
149+
if schemaPrepErr != nil {
150+
return schemaPrepErr
151+
}
152+
153153
return nil
154154
}
155+
156+
// from https://stackoverflow.com/a/40737676
157+
func convert(i interface{}) interface{} {
158+
switch x := i.(type) {
159+
case map[interface{}]interface{}:
160+
m2 := map[string]interface{}{}
161+
for k, v := range x {
162+
m2[k.(string)] = convert(v)
163+
}
164+
return m2
165+
case map[string]interface{}:
166+
m2 := map[string]interface{}{}
167+
for k, v := range x {
168+
m2[k] = convert(v)
169+
}
170+
return m2
171+
case []interface{}:
172+
for i, v := range x {
173+
x[i] = convert(v)
174+
}
175+
}
176+
return i
177+
}

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
}

0 commit comments

Comments
 (0)