mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
test for field conventions in json schema (#2642)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
96ee2db875
commit
8e62ff9831
119
syft/format/syftjson/schema_test.go
Normal file
119
syft/format/syftjson/schema_test.go
Normal file
@ -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
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user