package contract import ( "fmt" "strings" "git.ipao.vip/rogee/go-sip/contracts" "github.com/santhosh-tekuri/jsonschema/v6" "github.com/santhosh-tekuri/jsonschema/v6/kind" ) // ValidationFailure retains original bytes for private evidence, never Error/log output. type ValidationFailure struct { Surface string Resource string Raw []byte Reason string } func (e *ValidationFailure) Error() string { return e.Surface + " validation failed: " + e.Reason } func newValidationFailure(surface string, raw []byte, value any, cause error) error { // Discriminate the MQ branch only to explain an already proven validation failure. // This does not broaden acceptance or replace validation of the complete union. if surface == "mq" { if m, ok := value.(map[string]any); ok { p, _ := m["payload"].(map[string]any) if _, receipt := p["status"]; !receipt { names := map[string]string{"task.control": "mq-task-control-request.schema.json", "call.execute": "mq-call-execute-request.schema.json", "sip.config": "mq-sip-config.schema.json"} if name := names[fmt.Sprint(m["event_type"])]; name != "" { if s, err := contracts.CompileCurrent(name); err == nil { if detail := s.Validate(value); detail != nil { cause = detail } } } } } } var details []string var walk func(*jsonschema.ValidationError) walk = func(e *jsonschema.ValidationError) { if len(details) >= 32 { return } if len(e.Causes) > 0 { for _, c := range e.Causes { walk(c) } return } path := "/" + strings.Join(e.InstanceLocation, "/") if r, ok := e.ErrorKind.(*kind.Required); ok { for _, k := range r.Missing { details = append(details, strings.TrimSuffix(path, "/")+"/"+k+": required field missing") } return } details = append(details, path+": "+strings.Join(e.ErrorKind.KeywordPath(), "/")) } if e, ok := cause.(*jsonschema.ValidationError); ok { walk(e) } if len(details) == 0 { details = append(details, "structure does not match current Schema") } return &ValidationFailure{Surface: surface, Raw: append([]byte(nil), raw...), Reason: strings.Join(details, "; ")} }