Invalid Argument errors provide per field details. (#110)
* Invalid Argument errors now return a per field error message. * Creating an API error struct. * SDK uses field error details proto. * Adding tests specifically for handler helper error messages resulting in expected API output. * Inlining the error checks into the table driven tests. Use protocmp for assertions to make printing out diffs easier to parse. * Aggregate the invalid argument fields detected to be wrong. * Make the returned order of fields deterministic in the error details.. * Fixing formatting issue in errors_test.go. * Test to confirm unimplemented results in a 405 and not 404.
This commit is contained in:
1 parent
5333482ff8
commit
2c3fbe3024
10 files changed
+317
-186
No files matched your search
@@ -152,7 +152,7 @@ func (s Service) updateInRepo(ctx context.Context, orgID, projId string, mask []
|
||||
return nil, err
|
||||
}
|
||||
if len(dbMask) == 0 {
|
||||
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", []string{"update_mask"})
|
||||
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
|
||||
}
|
||||
repo, err := s.repo()
|
||||
if err != nil {
|
||||
@@ -194,7 +194,7 @@ func toDbUpdateMask(paths []string) ([]string, error) {
|
||||
}
|
||||
}
|
||||
if len(invalid) > 0 {
|
||||
return nil, handlers.InvalidArgumentErrorf(fmt.Sprintf("Invalid fields passed in update_update mask: %v.", invalid), []string{"update_mask"})
|
||||
return nil, handlers.InvalidArgumentErrorf(fmt.Sprintf("Invalid fields passed in update_update mask: %v.", invalid), map[string]string{"update_mask": fmt.Sprintf("Unknown paths provided in update mask: %q", strings.Join(invalid, ","))})
|
||||
}
|
||||
return dbPaths, nil
|
||||
}
|
||||
@@ -219,85 +219,76 @@ func toProto(in *iam.Scope) *pb.Project {
|
||||
// * The path passed in is correctly formatted
|
||||
// * All required parameters are set
|
||||
// * There are no conflicting parameters provided
|
||||
// TODO: Populate the error in a way to allow it to be converted to the previously described error format and include all invalid fields instead of just the most recent.
|
||||
func validateGetRequest(req *pbs.GetProjectRequest) error {
|
||||
if err := validateAncestors(req); err != nil {
|
||||
return err
|
||||
badFields := validateAncestors(req)
|
||||
if !validId(req.GetId(), "p_") {
|
||||
badFields["id"] = "Invalid formatted project id."
|
||||
}
|
||||
if !validId(req.GetId(), iam.ProjectScope.Prefix()+"_") {
|
||||
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", []string{"id"})
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCreateRequest(req *pbs.CreateProjectRequest) error {
|
||||
if err := validateAncestors(req); err != nil {
|
||||
return err
|
||||
}
|
||||
badFields := validateAncestors(req)
|
||||
item := req.GetItem()
|
||||
if item == nil {
|
||||
return handlers.InvalidArgumentErrorf("A project's fields must be set to something.", []string{"item"})
|
||||
}
|
||||
var immutableFieldsSet []string
|
||||
if item.GetId() != "" {
|
||||
immutableFieldsSet = append(immutableFieldsSet, "id")
|
||||
badFields["id"] = "This is a read only field."
|
||||
}
|
||||
if item.GetCreatedTime() != nil {
|
||||
immutableFieldsSet = append(immutableFieldsSet, "created_time")
|
||||
badFields["created_time"] = "This is a read only field."
|
||||
}
|
||||
if item.GetUpdatedTime() != nil {
|
||||
immutableFieldsSet = append(immutableFieldsSet, "updated_time")
|
||||
badFields["updated_time"] = "This is a read only field."
|
||||
}
|
||||
if len(immutableFieldsSet) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Cannot specify read only fields at creation time.", immutableFieldsSet)
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Argument errors found in the request.", badFields)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateUpdateRequest(req *pbs.UpdateProjectRequest) error {
|
||||
if err := validateAncestors(req); err != nil {
|
||||
return err
|
||||
badFields := validateAncestors(req)
|
||||
if !validId(req.GetId(), "p_") {
|
||||
badFields["project_id"] = "Improperly formatted path identifier."
|
||||
}
|
||||
if !validId(req.GetId(), iam.ProjectScope.Prefix()+"_") {
|
||||
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", []string{"id"})
|
||||
}
|
||||
|
||||
if req.GetUpdateMask() == nil {
|
||||
return handlers.InvalidArgumentErrorf("UpdateMask not provided but is required to update a project.", []string{"update_mask"})
|
||||
badFields["update_mask"] = "UpdateMask not provided but is required to update a project."
|
||||
}
|
||||
|
||||
item := req.GetItem()
|
||||
if item == nil {
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
|
||||
}
|
||||
// It is legitimate for no item to be specified in an update request as it indicates all fields provided in
|
||||
// the mask will be marked as unset.
|
||||
return nil
|
||||
}
|
||||
if item.GetId() != "" && item.GetId() != req.GetId() {
|
||||
return handlers.InvalidArgumentErrorf("Id in provided item and url do not match.", []string{"id"})
|
||||
}
|
||||
var immutableFieldsSet []string
|
||||
if item.GetId() != "" {
|
||||
immutableFieldsSet = append(immutableFieldsSet, "id")
|
||||
badFields["id"] = "This is a read only field and cannot be specified in an update request."
|
||||
}
|
||||
if item.GetCreatedTime() != nil {
|
||||
immutableFieldsSet = append(immutableFieldsSet, "created_time")
|
||||
badFields["created_time"] = "This is a read only field and cannot be specified in an update request."
|
||||
}
|
||||
if item.GetUpdatedTime() != nil {
|
||||
immutableFieldsSet = append(immutableFieldsSet, "updated_time")
|
||||
badFields["updated_time"] = "This is a read only field and cannot be specified in an update request."
|
||||
}
|
||||
if len(immutableFieldsSet) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Cannot specify read only fields at update time.", immutableFieldsSet)
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDeleteRequest(req *pbs.DeleteProjectRequest) error {
|
||||
if err := validateAncestors(req); err != nil {
|
||||
return err
|
||||
badFields := validateAncestors(req)
|
||||
if !validId(req.GetId(), "p_") {
|
||||
badFields["id"] = "Incorrectly formatted project."
|
||||
}
|
||||
if !validId(req.GetId(), iam.ProjectScope.Prefix()+"_") {
|
||||
return handlers.InvalidArgumentErrorf("Improperly formatted id.", []string{"id"})
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -315,12 +306,12 @@ type ancestorProvider interface {
|
||||
}
|
||||
|
||||
// validateAncestors verifies that the ancestors of this call are properly set and provided.
|
||||
func validateAncestors(r ancestorProvider) error {
|
||||
func validateAncestors(r ancestorProvider) map[string]string {
|
||||
if r.GetOrgId() == "" {
|
||||
return handlers.InvalidArgumentErrorf("Missing organization id.", []string{"org_id"})
|
||||
return map[string]string{"org_id": "Missing organization id."}
|
||||
}
|
||||
if !validId(r.GetOrgId(), iam.OrganizationScope.Prefix()+"_") {
|
||||
return handlers.InvalidArgumentErrorf("Poorly formatted org id.", []string{"org_id"})
|
||||
return map[string]string{"org_id": "Improperly formatted identifier."}
|
||||
}
|
||||
return nil
|
||||
return map[string]string{}
|
||||
}
|
||||
Reference in new issue
Block a user