Skip to content

Commit

Permalink
feat: add snippet validator, fixes #101
Browse files Browse the repository at this point in the history
  • Loading branch information
shyim committed Jan 13, 2024
1 parent c4513c5 commit e812ea6
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 0 deletions.
225 changes: 225 additions & 0 deletions extension/snippet_validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package extension

import (
"fmt"
"os"
"path"
"path/filepath"
"reflect"
"strings"

"github.com/wI2L/jsondiff"
)

func validateStorefrontSnippets(context *ValidationContext) {
rootDir := context.Extension.GetRootDir()
if err := validateStorefrontSnippetsByPath(rootDir, context); err != nil {
return
}

for _, extraBundle := range context.Extension.GetExtensionConfig().Build.ExtraBundles {
bundlePath := rootDir

if extraBundle.Path != "" {
bundlePath = path.Join(bundlePath, extraBundle.Path)
} else {
bundlePath = path.Join(bundlePath, extraBundle.Name)
}

if err := validateStorefrontSnippetsByPath(bundlePath, context); err != nil {
return
}
}
}

func validateStorefrontSnippetsByPath(extensionRoot string, context *ValidationContext) error {
snippetFolder := path.Join(extensionRoot, "Resources", "snippet")

if _, err := os.Stat(snippetFolder); err != nil {
return nil //nolint:nilerr
}

dirEntries, err := os.ReadDir(snippetFolder)
if err != nil {
return err
}

files := make([]string, 0)

for _, dirEntry := range dirEntries {
if dirEntry.IsDir() {
continue
}

if !strings.HasSuffix(dirEntry.Name(), ".json") {
continue
}

files = append(files, path.Join(snippetFolder, dirEntry.Name()))
}

if len(files) == 1 {
// We have no other file to compare against
return nil
}

var mainFile string

for _, file := range files {
if strings.HasSuffix(filepath.Base(file), "en-GB.json") {
mainFile = file
}
}

if len(mainFile) == 0 {
context.AddWarning(fmt.Sprintf("No en-GB.json file found in %s, using %s", snippetFolder, files[0]))
mainFile = files[0]
}

mainFileContent, err := os.ReadFile(mainFile)
if err != nil {
return err
}

for _, file := range files {
// makes no sense to compare to ourself
if file == mainFile {
continue
}

if err := compareSnippets(mainFileContent, file, context, extensionRoot); err != nil {
return err
}
}

return nil
}

func validateAdministrationSnippets(context *ValidationContext) {
rootDir := context.Extension.GetRootDir()
if err := validateAdministrationByPath(rootDir, context); err != nil {
return
}

for _, extraBundle := range context.Extension.GetExtensionConfig().Build.ExtraBundles {
bundlePath := rootDir

if extraBundle.Path != "" {
bundlePath = path.Join(bundlePath, extraBundle.Path)
} else {
bundlePath = path.Join(bundlePath, extraBundle.Name)
}

if err := validateAdministrationByPath(bundlePath, context); err != nil {
return
}
}
}

func validateAdministrationByPath(extensionRoot string, context *ValidationContext) error {
adminFolder := path.Join(extensionRoot, "Resources", "app", "administration")

if _, err := os.Stat(adminFolder); err != nil {
return nil //nolint:nilerr
}

snippetFiles := make(map[string][]string)

err := filepath.WalkDir(adminFolder, func(path string, d os.DirEntry, err error) error {
if d.IsDir() {
return nil
}

if filepath.Ext(path) != ".json" {
return nil
}

containingFolder := filepath.Dir(path)

if filepath.Base(containingFolder) != "snippet" {
return nil
}

if _, ok := snippetFiles[containingFolder]; !ok {
snippetFiles[containingFolder] = []string{}
}

snippetFiles[containingFolder] = append(snippetFiles[containingFolder], path)

return nil
})
if err != nil {
return err
}

for folder, files := range snippetFiles {
if len(files) == 1 {
// We have no other file to compare against
continue
}

var mainFile string

for _, file := range files {
if strings.HasSuffix(filepath.Base(file), "en-GB.json") {
mainFile = file
}
}

if len(mainFile) == 0 {
context.AddWarning(fmt.Sprintf("No en-GB.json file found in %s, using %s", folder, files[0]))
mainFile = files[0]
}

mainFileContent, err := os.ReadFile(mainFile)
if err != nil {
return err
}

for _, file := range files {
// makes no sense to compare to ourself
if file == mainFile {
continue
}

if err := compareSnippets(mainFileContent, file, context, extensionRoot); err != nil {
return err
}
}
}

return nil
}

func compareSnippets(mainFile []byte, file string, context *ValidationContext, extensionRoot string) error {
checkFile, err := os.ReadFile(file)
if err != nil {
return err
}

compare, err := jsondiff.CompareJSON(mainFile, checkFile)
if err != nil {
return err
}

for _, diff := range compare {
normalizedPath := strings.ReplaceAll(file, extensionRoot+"/", "")

if diff.Type == jsondiff.OperationReplace && reflect.TypeOf(diff.OldValue) != reflect.TypeOf(diff.Value) {
context.AddError(fmt.Sprintf("Snippet file: %s, key: %s, has the type %s, but in the main language it is %s", normalizedPath, diff.Path, reflect.TypeOf(diff.OldValue), reflect.TypeOf(diff.Value)))
continue
}

if diff.Type == jsondiff.OperationAdd {
context.AddError(fmt.Sprintf("Snippet file: %s, missing key \"%s\" in this snippet file, but defined in the main language", normalizedPath, diff.Path))
continue
}

if diff.Type == jsondiff.OperationRemove {
context.AddError(fmt.Sprintf("Snippet file: %s, key: %s, is not defined in the main language", normalizedPath, diff.Path))
continue
}
}

return nil
}
79 changes: 79 additions & 0 deletions extension/snippet_validator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package extension

import (
"os"
"path"
"testing"

"github.com/stretchr/testify/assert"
)

func TestSnippetValidateNoExistingFolderAdmin(t *testing.T) {
context := newValidationContext(PlatformPlugin{
path: "test",
config: &Config{},
})

validateAdministrationSnippets(context)
}

func TestSnippetValidateNoExistingFolderStorefront(t *testing.T) {
context := newValidationContext(PlatformPlugin{
path: "test",
config: &Config{},
})

validateAdministrationSnippets(context)
}

func TestSnippetValidateStorefrontByPathOneFileIsIgnored(t *testing.T) {
tmpDir := t.TempDir()

context := newValidationContext(PlatformPlugin{
path: tmpDir,
config: &Config{},
})

_ = os.MkdirAll(path.Join(tmpDir, "Resources", "snippet"), os.ModePerm)
_ = os.WriteFile(path.Join(tmpDir, "Resources", "snippet", "storefront.en-GB.json"), []byte(`{}`), os.ModePerm)

assert.NoError(t, validateStorefrontSnippetsByPath(tmpDir, context))
assert.Len(t, context.errors, 0)
assert.Len(t, context.warnings, 0)
}

func TestSnippetValidateStorefrontByPathSameFile(t *testing.T) {
tmpDir := t.TempDir()

context := newValidationContext(PlatformPlugin{
path: tmpDir,
config: &Config{},
})

_ = os.MkdirAll(path.Join(tmpDir, "Resources", "snippet"), os.ModePerm)
_ = os.WriteFile(path.Join(tmpDir, "Resources", "snippet", "storefront.en-GB.json"), []byte(`{"test": "1"}`), os.ModePerm)
_ = os.WriteFile(path.Join(tmpDir, "Resources", "snippet", "storefront.de-DE.json"), []byte(`{"test": "2"}`), os.ModePerm)

assert.NoError(t, validateStorefrontSnippetsByPath(tmpDir, context))
assert.Len(t, context.errors, 0)
assert.Len(t, context.warnings, 0)
}

func TestSnippetValidateStorefrontByPathTestDifferent(t *testing.T) {
tmpDir := t.TempDir()

context := newValidationContext(PlatformPlugin{
path: tmpDir,
config: &Config{},
})

_ = os.MkdirAll(path.Join(tmpDir, "Resources", "snippet"), os.ModePerm)
_ = os.WriteFile(path.Join(tmpDir, "Resources", "snippet", "storefront.en-GB.json"), []byte(`{"a": "1"}`), os.ModePerm)
_ = os.WriteFile(path.Join(tmpDir, "Resources", "snippet", "storefront.de-DE.json"), []byte(`{"b": "2"}`), os.ModePerm)

assert.NoError(t, validateStorefrontSnippetsByPath(tmpDir, context))
assert.Len(t, context.errors, 2)
assert.Len(t, context.warnings, 0)
assert.Equal(t, "Snippet file: Resources/snippet/storefront.de-DE.json, key: /a, is not defined in the main language", context.errors[0])
assert.Equal(t, "Snippet file: Resources/snippet/storefront.de-DE.json, missing key \"/b\" in this snippet file, but defined in the main language", context.errors[1])
}
2 changes: 2 additions & 0 deletions extension/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ func RunValidation(ctx context.Context, ext Extension) *ValidationContext {

runDefaultValidate(context)
ext.Validate(ctx, context)
validateAdministrationSnippets(context)
validateStorefrontSnippets(context)

return context
}
Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tidwall/gjson v1.17.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/wI2L/jsondiff v0.5.0 // indirect
golang.org/x/sync v0.5.0 // indirect
)

Expand Down
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,19 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g=
github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/vulcand/oxy/v2 v2.0.0 h1:V+scHhd2xBjO8ojBRgxCM+OdZxRA/YTs8M70w5tdNy8=
github.com/vulcand/oxy/v2 v2.0.0/go.mod h1:uIAz3sYafO7i+V3SC8oDlMn/lt1i9aWcyXuXqVswKzE=
github.com/wI2L/jsondiff v0.5.0 h1:RRMTi/mH+R2aXcPe1VYyvGINJqQfC3R+KSEakuU1Ikw=
github.com/wI2L/jsondiff v0.5.0/go.mod h1:qqG6hnK0Lsrz2BpIVCxWiK9ItsBCpIZQiv0izJjOZ9s=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
Expand Down

0 comments on commit e812ea6

Please sign in to comment.