-
Notifications
You must be signed in to change notification settings - Fork 599
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test for field conventions in json schema (#2642)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
- Loading branch information
Showing
1 changed file
with
119 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |