mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 09:01:31 +00:00
142 lines
3.7 KiB
Go
142 lines
3.7 KiB
Go
package awsmodel
|
|
|
|
import (
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Rule identifies a model constraint violated by a response document.
|
|
type Rule string
|
|
|
|
const (
|
|
RuleUnknownField Rule = "unknown_field"
|
|
RuleRequiredMember Rule = "required_member"
|
|
RuleEnum Rule = "enum"
|
|
RuleErrorCode Rule = "error_code"
|
|
RuleHTTPStatus Rule = "http_status"
|
|
)
|
|
|
|
// Violation describes one response value that does not conform to its output
|
|
// shape. Path uses a JSONPath-like notation rooted at $.
|
|
type Violation struct {
|
|
Rule Rule
|
|
Path string
|
|
Message string
|
|
}
|
|
|
|
func (v Violation) String() string {
|
|
return fmt.Sprintf("%s: %s (%s)", v.Path, v.Message, v.Rule)
|
|
}
|
|
|
|
// Validate checks a normalized response document against an operation's
|
|
// output shape. Structure keys must use the model's member names;
|
|
// protocol-specific response middleware is responsible for translating wire
|
|
// names and wrappers to that representation.
|
|
func Validate(service Service, operationName string, document any) ([]Violation, error) {
|
|
model, err := Load(service)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
operation, ok := model.Operation(operationName)
|
|
if !ok {
|
|
return nil, fmt.Errorf("awsmodel: %s operation %q is not modelled", service, operationName)
|
|
}
|
|
if operation.Output == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
var violations []Violation
|
|
model.validateShape(operation.Output.Shape, "$", document, &violations)
|
|
return violations, nil
|
|
}
|
|
|
|
func (m *Model) validateShape(shapeName, path string, value any, violations *[]Violation) {
|
|
shape := m.shapes[shapeName]
|
|
if shape == nil {
|
|
// Load verifies references, so this is unreachable for a loaded model.
|
|
return
|
|
}
|
|
|
|
switch shape.Type {
|
|
case "structure":
|
|
m.validateStructure(shape, path, value, violations)
|
|
case "list":
|
|
values, ok := value.([]any)
|
|
if !ok {
|
|
return
|
|
}
|
|
for index, item := range values {
|
|
m.validateShape(shape.Member.Shape, fmt.Sprintf("%s[%d]", path, index), item, violations)
|
|
}
|
|
case "map":
|
|
values, ok := value.(map[string]any)
|
|
if !ok {
|
|
return
|
|
}
|
|
keys := slices.Sorted(maps.Keys(values))
|
|
for _, key := range keys {
|
|
keyPath := path + "[" + strconv.Quote(key) + "]"
|
|
m.validateShape(shape.Key.Shape, keyPath+".key", key, violations)
|
|
m.validateShape(shape.Value.Shape, keyPath, values[key], violations)
|
|
}
|
|
case "string":
|
|
stringValue, ok := value.(string)
|
|
if !ok || len(shape.Enum) == 0 {
|
|
return
|
|
}
|
|
if !slices.Contains(shape.Enum, stringValue) {
|
|
*violations = append(*violations, Violation{
|
|
Rule: RuleEnum,
|
|
Path: path,
|
|
Message: fmt.Sprintf("value %q is not one of [%s]", stringValue, strings.Join(shape.Enum, ", ")),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *Model) validateStructure(shape *Shape, path string, value any, violations *[]Violation) {
|
|
fields, ok := value.(map[string]any)
|
|
if !ok {
|
|
fields = map[string]any{}
|
|
}
|
|
|
|
required := append([]string(nil), shape.Required...)
|
|
slices.Sort(required)
|
|
for _, name := range required {
|
|
if _, present := fields[name]; !present {
|
|
*violations = append(*violations, Violation{
|
|
Rule: RuleRequiredMember,
|
|
Path: memberPath(path, name),
|
|
Message: "required member is missing",
|
|
})
|
|
}
|
|
}
|
|
|
|
fieldNames := slices.Sorted(maps.Keys(fields))
|
|
for _, name := range fieldNames {
|
|
if _, modelled := shape.Members[name]; !modelled {
|
|
*violations = append(*violations, Violation{
|
|
Rule: RuleUnknownField,
|
|
Path: memberPath(path, name),
|
|
Message: "field is not present in the model shape",
|
|
})
|
|
}
|
|
}
|
|
|
|
memberNames := slices.Sorted(maps.Keys(shape.Members))
|
|
for _, name := range memberNames {
|
|
field, present := fields[name]
|
|
if !present {
|
|
continue
|
|
}
|
|
m.validateShape(shape.Members[name].Shape, memberPath(path, name), field, violations)
|
|
}
|
|
}
|
|
|
|
func memberPath(parent, name string) string {
|
|
return parent + "." + name
|
|
}
|