internal/api/arm/error.go (141 lines of code) (raw):

// Copyright 2025 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package arm import ( "encoding/json" "fmt" "net/http" azcorearm "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" ) // CloudError codes const ( CloudErrorCodeInternalServerError = "InternalServerError" CloudErrorCodeInvalidParameter = "InvalidParameter" CloudErrorCodeInvalidRequestContent = "InvalidRequestContent" CloudErrorCodeInvalidResource = "InvalidResource" CloudErrorCodeInvalidResourceType = "InvalidResourceType" CloudErrorCodeMultipleErrorsOccurred = "MultipleErrorsOccurred" CloudErrorCodeUnsupportedMediaType = "UnsupportedMediaType" CloudErrorCodeCanceled = "Canceled" CloudErrorCodeConflict = "Conflict" CloudErrorCodeNotFound = "NotFound" CloudErrorCodeInvalidSubscriptionState = "InvalidSubscriptionState" CloudErrorCodeSubscriptionNotFound = "SubscriptionNotFound" CloudErrorCodeResourceNotFound = "ResourceNotFound" CloudErrorCodeResourceGroupNotFound = "ResourceGroupNotFound" CloudErrorCodeInvalidSubscriptionID = "InvalidSubscriptionID" CloudErrorCodeInvalidResourceName = "InvalidResourceName" CloudErrorCodeInvalidResourceGroupName = "InvalidResourceGroupName" ) // CloudError represents a complete resource provider error. type CloudError struct { // The HTTP status code StatusCode int `json:"-"` // The response body to be converted to JSON *CloudErrorBody `json:"error,omitempty"` } func (err *CloudError) Error() string { var body string if err.CloudErrorBody != nil { body = ": " + err.String() } return fmt.Sprintf("%d%s", err.StatusCode, body) } // CloudErrorBody represents the structure of the response body for a resource provider error. // See https://github.com/cloud-and-ai-microsoft/resource-provider-contract/blob/master/v1.0/common-api-details.md#error-response-content type CloudErrorBody struct { // An identifier for the error. Codes are invariant and are intended to be consumed programmatically. Code string `json:"code,omitempty"` // A message describing the error, intended to be suitable for display in a user interface. Message string `json:"message,omitempty"` // The target of the particular error. For example, the name of the property in error. Target string `json:"target,omitempty"` // A list of additional details about the error. Details []CloudErrorBody `json:"details,omitempty"` } func (body *CloudErrorBody) String() string { out := fmt.Sprintf("%s: ", body.Code) if len(body.Target) > 0 { out += fmt.Sprintf("%s: ", body.Target) } out += body.Message if len(body.Details) > 0 { out += " Details: " for i, innerErr := range body.Details { out += innerErr.String() if i < len(body.Details)-1 { out += ", " } } } return out } // NewCloudError returns a new CloudError func NewCloudError(statusCode int, code, target, format string, a ...interface{}) *CloudError { return &CloudError{ StatusCode: statusCode, CloudErrorBody: &CloudErrorBody{ Code: code, Message: fmt.Sprintf(format, a...), Target: target, }, } } // WriteError constructs and writes a CloudError to the given ResponseWriter func WriteError(w http.ResponseWriter, statusCode int, code, target, format string, a ...interface{}) { WriteCloudError(w, NewCloudError(statusCode, code, target, format, a...)) } // WriteCloudError writes a CloudError to the given ResponseWriter func WriteCloudError(w http.ResponseWriter, err *CloudError) { w.Header()[HeaderNameErrorCode] = []string{err.Code} _, _ = WriteJSONResponse(w, err.StatusCode, err) } // NewInternalServerError creates a CloudError for an internal server error func NewInternalServerError() *CloudError { return NewCloudError( http.StatusInternalServerError, CloudErrorCodeInternalServerError, "", "Internal server error.") } // WriteInternalServerError writes an internal server error to the given ResponseWriter func WriteInternalServerError(w http.ResponseWriter) { WriteCloudError(w, NewInternalServerError()) } // NewConflictError creates a CloudError for a conflict error func NewConflictError(resourceID *azcorearm.ResourceID, format string, a ...interface{}) *CloudError { return NewCloudError( http.StatusConflict, CloudErrorCodeConflict, resourceID.String(), format, a...) } // WriteConflictError writes a conflict error to the given ResponseWriter func WriteConflictError(w http.ResponseWriter, resourceID *azcorearm.ResourceID, format string, a ...interface{}) { WriteCloudError(w, NewConflictError(resourceID, format, a...)) } // NewResourceNotFoundError creates a CloudError for a nonexistent resource error func NewResourceNotFoundError(resourceID *azcorearm.ResourceID) *CloudError { var code string var message string switch resourceID.ResourceType.String() { case azcorearm.SubscriptionResourceType.String(): code = CloudErrorCodeSubscriptionNotFound message = fmt.Sprintf( "The subscription '%s' was not found.", resourceID.SubscriptionID) case azcorearm.ResourceGroupResourceType.String(): code = CloudErrorCodeResourceGroupNotFound message = fmt.Sprintf( "The resource group '%s' under subscription '%s' was not found.", resourceID.ResourceGroupName, resourceID.SubscriptionID) default: code = CloudErrorCodeResourceNotFound message = fmt.Sprintf( "The resource '%s/%s' under resource group '%s' was not found.", resourceID.ResourceType.Type, resourceID.Name, resourceID.ResourceGroupName) } return NewCloudError(http.StatusNotFound, code, resourceID.String(), "%s", message) } // WriteResourceNotFoundError writes a nonexistent resource error to the given ResponseWriter func WriteResourceNotFoundError(w http.ResponseWriter, resourceID *azcorearm.ResourceID) { WriteCloudError(w, NewResourceNotFoundError(resourceID)) } // NewInvalidRequestContentError creates a CloudError for an invalid request content error func NewInvalidRequestContentError(err error) *CloudError { const message = "The request content was invalid and could not be deserialized: %q" switch err := err.(type) { case *CloudError: return err case *json.UnmarshalTypeError: return NewCloudError( http.StatusBadRequest, CloudErrorCodeInvalidRequestContent, err.Field, message, err) default: return NewCloudError( http.StatusBadRequest, CloudErrorCodeInvalidRequestContent, "", message, err) } } // WriteInvalidRequestContentError writes an invalid request content error to the given ResponseWriter func WriteInvalidRequestContentError(w http.ResponseWriter, err error) { WriteCloudError(w, NewInvalidRequestContentError(err)) }