Skip to content

Commit

Permalink
test for field conventions in json schema (#2642)
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
  • Loading branch information
wagoodman authored Feb 14, 2024
1 parent 96ee2db commit 8e62ff9
Showing 1 changed file with 119 additions and 0 deletions.
119 changes: 119 additions & 0 deletions syft/format/syftjson/schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package syftjson

import (
"encoding/json"
"os"
"path/filepath"
"testing"

"github.com/iancoleman/strcase"
"github.com/stretchr/testify/require"

"github.com/anchore/syft/syft/internal/packagemetadata"
)

type schema struct {
Schema string `json:"$schema"`
ID string `json:"$id"`
Ref string `json:"$ref"`
Defs map[string]properties `json:"$defs"`
}

type properties struct {
Properties map[string]any `json:"properties"`
Type string `json:"type"`
Required []string `json:"required"`
}

func (p properties) fields() []string {
var result []string

for k := range p.Properties {
result = append(result, k)
}
return result
}

func Test_JSONSchemaConventions(t *testing.T) {
// read schema/json/schema-latest.json
// look at all attributes and ensure that all fields are camelCase
// we want to strictly follow https://google.github.io/styleguide/javaguide.html#s5.3-camel-case
//
// > Convert the phrase to plain ASCII and remove any apostrophes. For example, "Müller's algorithm" might become "Muellers algorithm".
// > Divide this result into words, splitting on spaces and any remaining punctuation (typically hyphens).
// > Recommended: if any word already has a conventional camel-case appearance in common usage, split this into its constituent parts (e.g., "AdWords" becomes "ad words"). Note that a word such as "iOS" is not really in camel case per se; it defies any convention, so this recommendation does not apply.
// > Now lowercase everything (including acronyms), then uppercase only the first character of:
// > ... each word, to yield upper camel case, or
// > ... each word except the first, to yield lower camel case
// > Finally, join all the words into a single identifier.
//
// This means that acronyms should be treated as words (e.g. "HttpServer" not "HTTPServer")

root, err := packagemetadata.RepoRoot()
require.NoError(t, err)

contents, err := os.ReadFile(filepath.Join(root, "schema", "json", "schema-latest.json"))
require.NoError(t, err)

var s schema
require.NoError(t, json.Unmarshal(contents, &s))

require.NotEmpty(t, s.Defs)

for name, def := range s.Defs {
checkAndConvertFields(t, name, def.fields())
}
}

func checkAndConvertFields(t *testing.T, path string, properties []string) {
for _, fieldName := range properties {
if pass, exp := isFollowingConvention(path, fieldName); !pass {
t.Logf("%s: has non camel case field: %q (expected %q)", path, fieldName, exp)
}

}
}

func isFollowingConvention(path, fieldName string) (bool, string) {
exp := strcase.ToLowerCamel(fieldName)
result := exp == fieldName

exception := func(exceptions ...string) (bool, string) {
for _, e := range exceptions {
if e == fieldName {
return true, fieldName
}
}
return result, exp
}

// add exceptions as needed... these are grandfathered in and will be addressed in a future breaking schema change
// ideally in the future there will be no exceptions to the camel case convention for fields
switch path {
case "Coordinates", "Location":
return exception("layerID")
case "MicrosoftKbPatch":
return exception("product_id")
case "HaskellHackageStackLockEntry":
return exception("snapshotURL")
case "LinuxRelease":
return exception("imageID", "supportURL", "privacyPolicyURL", "versionID", "variantID", "homeURL", "buildID", "bugReportURL")
case "CConanLockV2Entry":
return exception("packageID")
case "CConanInfoEntry":
return exception("package_id")
case "PhpComposerInstalledEntry", "PhpComposerLockEntry":
return exception("notification-url", "require-dev")
case "LinuxKernelArchive":
return exception("rwRootFS")
case "CConanLockEntry":
return exception("build_requires", "py_requires", "package_id")
case "FileMetadataEntry":
return exception("userID", "groupID")
case "DartPubspecLockEntry":
return exception("hosted_url", "vcs_url")
case "ELFSecurityFeatures":
return exception("relRO")
}
return result, exp
}

0 comments on commit 8e62ff9

Please sign in to comment.