mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 17:11:30 +00:00
143 lines
4.1 KiB
Go
143 lines
4.1 KiB
Go
package awsmodel
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// ValidateErrorResponse decodes an AWS error envelope and checks its code
|
|
// against the errors declared for operation. Modelled is false when AWS's
|
|
// operation definition declares no errors, as is the case for some STS calls.
|
|
func ValidateErrorResponse(service Service, operationName string, body []byte) (violations []Violation, modelled bool, err error) {
|
|
model, err := Load(service)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
operation, ok := model.Operation(operationName)
|
|
if !ok {
|
|
return nil, false, fmt.Errorf("awsmodel: %s operation %q is not modelled", service, operationName)
|
|
}
|
|
|
|
code, err := model.decodeErrorCode(body)
|
|
if err != nil {
|
|
return nil, false, fmt.Errorf("awsmodel: decode %s %s error response: %w", service, operationName, err)
|
|
}
|
|
if isUnmodelledCommonError(service, code) {
|
|
return nil, false, nil
|
|
}
|
|
codes := model.operationErrorCodes(operation)
|
|
if len(codes) == 0 {
|
|
return nil, false, nil
|
|
}
|
|
if slices.Contains(codes, code) {
|
|
return nil, true, nil
|
|
}
|
|
return []Violation{{
|
|
Rule: RuleErrorCode,
|
|
Path: "$error.Code",
|
|
Message: fmt.Sprintf("error code %q is not declared; allowed [%s]", code, strings.Join(codes, ", ")),
|
|
}}, true, nil
|
|
}
|
|
|
|
// isUnmodelledCommonError identifies service/protocol errors AWS does not put
|
|
// in operation error lists. They remain counted as unmodelled by the harness;
|
|
// they are not treated as proof that an operation-specific code conforms.
|
|
func isUnmodelledCommonError(service Service, code string) bool {
|
|
switch service {
|
|
case IAM, ElasticLoadBalancingV2:
|
|
return isUnmodelledQueryError(code)
|
|
case STS:
|
|
// STS also uses these two runtime input errors even though api-2.json
|
|
// does not declare them on AssumeRole/GetSessionToken.
|
|
return isUnmodelledQueryError(code) || code == "ValidationError" || code == "InvalidParameterValue"
|
|
case ECS:
|
|
switch code {
|
|
case "AccessDeniedException", "IncompleteSignatureException", "InvalidClientTokenIdException",
|
|
"MissingAuthenticationTokenException", "RequestExpiredException", "SignatureDoesNotMatchException",
|
|
"ThrottlingException":
|
|
return true
|
|
}
|
|
default:
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isUnmodelledQueryError(code string) bool {
|
|
switch code {
|
|
case "AccessDenied", "IncompleteSignature", "InvalidClientTokenId", "MissingAuthenticationToken",
|
|
"RequestExpired", "SignatureDoesNotMatch", "Throttling":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (m *Model) operationErrorCodes(operation *Operation) []string {
|
|
codes := make([]string, 0, len(operation.Errors))
|
|
for _, ref := range operation.Errors {
|
|
code := ref.Shape
|
|
if shape := m.shapes[ref.Shape]; shape != nil && shape.Error != nil && shape.Error.Code != "" {
|
|
code = shape.Error.Code
|
|
}
|
|
codes = append(codes, code)
|
|
}
|
|
sort.Strings(codes)
|
|
return codes
|
|
}
|
|
|
|
func (m *Model) decodeErrorCode(body []byte) (string, error) {
|
|
switch m.metadata.Protocol {
|
|
case "json", "rest-json":
|
|
var envelope struct {
|
|
Type string `json:"__type"`
|
|
Code string `json:"code"`
|
|
}
|
|
decoder := json.NewDecoder(bytes.NewReader(body))
|
|
if err := decoder.Decode(&envelope); err != nil {
|
|
return "", err
|
|
}
|
|
code := envelope.Type
|
|
if code == "" {
|
|
code = envelope.Code
|
|
}
|
|
if separator := strings.LastIndex(code, "#"); separator >= 0 {
|
|
code = code[separator+1:]
|
|
}
|
|
if separator := strings.Index(code, ":"); separator >= 0 {
|
|
code = code[:separator]
|
|
}
|
|
if code == "" {
|
|
return "", fmt.Errorf("JSON error envelope has no code")
|
|
}
|
|
return code, nil
|
|
case "ec2", "query":
|
|
root, err := parseXML(body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
code := findXMLText(root, "Code")
|
|
if code == "" {
|
|
return "", fmt.Errorf("XML error envelope has no Code element")
|
|
}
|
|
return code, nil
|
|
default:
|
|
return "", fmt.Errorf("error decoding for protocol %q is not implemented", m.metadata.Protocol)
|
|
}
|
|
}
|
|
|
|
func findXMLText(node *xmlNode, name string) string {
|
|
if node.name == name {
|
|
return strings.TrimSpace(node.text.String())
|
|
}
|
|
for _, child := range node.children {
|
|
if value := findXMLText(child, name); value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|