Expose validation errors through the root API
This commit is contained in:
@@ -70,10 +70,91 @@ func checkRepository(ctx context.Context, dir string) error {
|
||||
if violations := checkExports(pkg); len(violations) != 0 {
|
||||
return fmt.Errorf("facade %s has invalid public contracts:\n %s", path, strings.Join(violations, "\n "))
|
||||
}
|
||||
if path == modulePath {
|
||||
if violations := checkValidationTypes(pkg); len(violations) != 0 {
|
||||
return fmt.Errorf("facade %s has invalid validation diagnostics:\n %s", path, strings.Join(violations, "\n "))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkValidationTypes(pkg *types.Package) []string {
|
||||
var violations []string
|
||||
for _, name := range []string{"ValidationCheck", "ValidationFinding", "FileSizeMeasurement", "ValidationError"} {
|
||||
object, ok := pkg.Scope().Lookup(name).(*types.TypeName)
|
||||
if !ok {
|
||||
violations = append(violations, fmt.Sprintf("missing facade-owned type %s", name))
|
||||
continue
|
||||
}
|
||||
named, ok := types.Unalias(object.Type()).(*types.Named)
|
||||
if !ok || named.Obj().Pkg() == nil || named.Obj().Pkg().Path() != pkg.Path() {
|
||||
violations = append(violations, fmt.Sprintf("%s is not a facade-owned named type", name))
|
||||
}
|
||||
}
|
||||
validationError, ok := pkg.Scope().Lookup("ValidationError").(*types.TypeName)
|
||||
if !ok {
|
||||
return violations
|
||||
}
|
||||
named, ok := types.Unalias(validationError.Type()).(*types.Named)
|
||||
if !ok {
|
||||
return violations
|
||||
}
|
||||
structure, ok := named.Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return append(violations, "ValidationError is not a struct")
|
||||
}
|
||||
fields := map[string]string{
|
||||
"Check": "ValidationCheck",
|
||||
"Findings": "[]ValidationFinding",
|
||||
"Measurement": "*FileSizeMeasurement",
|
||||
}
|
||||
for fieldName, expected := range fields {
|
||||
fieldIndex := -1
|
||||
for index := 0; index < structure.NumFields(); index++ {
|
||||
if structure.Field(index).Name() == fieldName && structure.Field(index).Exported() {
|
||||
fieldIndex = index
|
||||
break
|
||||
}
|
||||
}
|
||||
if fieldIndex < 0 || !validationFieldMatches(structure.Field(fieldIndex).Type(), expected, pkg.Path()) {
|
||||
violations = append(violations, fmt.Sprintf("ValidationError.%s must expose %s", fieldName, expected))
|
||||
}
|
||||
}
|
||||
if cause := fieldByName(structure, "Cause"); cause == nil || !cause.Exported() || !types.Identical(cause.Type(), types.Universe.Lookup("error").Type()) {
|
||||
violations = append(violations, "ValidationError.Cause must expose error")
|
||||
}
|
||||
return violations
|
||||
}
|
||||
|
||||
func fieldByName(structure *types.Struct, name string) *types.Var {
|
||||
for index := 0; index < structure.NumFields(); index++ {
|
||||
if structure.Field(index).Name() == name {
|
||||
return structure.Field(index)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validationFieldMatches(fieldType types.Type, expected, packagePath string) bool {
|
||||
switch expected {
|
||||
case "[]ValidationFinding":
|
||||
slice, ok := types.Unalias(fieldType).(*types.Slice)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
fieldType = slice.Elem()
|
||||
case "*FileSizeMeasurement":
|
||||
pointer, ok := types.Unalias(fieldType).(*types.Pointer)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
fieldType = pointer.Elem()
|
||||
}
|
||||
named, ok := types.Unalias(fieldType).(*types.Named)
|
||||
return ok && named.Obj().Pkg() != nil && named.Obj().Pkg().Path() == packagePath && named.Obj().Name() == strings.TrimLeft(expected, "*[]")
|
||||
}
|
||||
|
||||
func listPackages(ctx context.Context, dir string) ([]listedPackage, error) {
|
||||
cmd := exec.CommandContext(ctx, "go", "list", "-deps", "-export", "-json", "./...")
|
||||
cmd.Dir = dir
|
||||
|
||||
Reference in New Issue
Block a user