package jsonschema import ( "encoding/json" "fmt" "strings" "github.com/santhosh-tekuri/jsonschema/v6/kind" "golang.org/x/text/language" "golang.org/x/text/message" ) var defaultPrinter = message.NewPrinter(language.English) // format --- func (e *ValidationError) schemaURL() string { if ref, ok := e.ErrorKind.(*kind.Reference); ok { return ref.URL } else { return e.SchemaURL } } func (e *ValidationError) absoluteKeywordLocation() string { var schemaURL string var keywordPath []string if ref, ok := e.ErrorKind.(*kind.Reference); ok { schemaURL = ref.URL keywordPath = nil } else { schemaURL = e.SchemaURL keywordPath = e.ErrorKind.KeywordPath() } return fmt.Sprintf("%s%s", schemaURL, encode(jsonPtr(keywordPath))) } func (e *ValidationError) skip() bool { if len(e.Causes) == 1 { _, ok := e.ErrorKind.(*kind.Reference) return ok } return false } func (e *ValidationError) display(sb *strings.Builder, verbose bool, indent int, absKwLoc string, p *message.Printer) { if !e.skip() { if indent > 0 { sb.WriteByte('\n') for i := 0; i < indent-1; i++ { sb.WriteString(" ") } sb.WriteString("- ") } indent = indent + 1 prevAbsKwLoc := absKwLoc absKwLoc = e.absoluteKeywordLocation() if _, ok := e.ErrorKind.(*kind.Schema); ok { sb.WriteString(e.ErrorKind.LocalizedString(p)) } else { sb.WriteString(p.Sprintf("at %s", quote(jsonPtr(e.InstanceLocation)))) if verbose { schLoc := absKwLoc if prevAbsKwLoc != "" { pu, _ := split(prevAbsKwLoc) u, f := split(absKwLoc) if u == pu { schLoc = fmt.Sprintf("S#%s", f) } } fmt.Fprintf(sb, " [%s]", schLoc) } fmt.Fprintf(sb, ": %s", e.ErrorKind.LocalizedString(p)) } } for _, cause := range e.Causes { cause.display(sb, verbose, indent, absKwLoc, p) } } func (e *ValidationError) Error() string { return e.LocalizedError(defaultPrinter) } func (e *ValidationError) LocalizedError(p *message.Printer) string { var sb strings.Builder e.display(&sb, false, 0, "", p) return sb.String() } func (e *ValidationError) GoString() string { return e.LocalizedGoString(defaultPrinter) } func (e *ValidationError) LocalizedGoString(p *message.Printer) string { var sb strings.Builder e.display(&sb, true, 0, "", p) return sb.String() } func jsonPtr(tokens []string) string { var sb strings.Builder for _, tok := range tokens { sb.WriteByte('/') sb.WriteString(escape(tok)) } return sb.String() } // -- // Flag is output format with simple boolean property valid. type FlagOutput struct { Valid bool `json:"valid"` } // The `Flag` output format, merely the boolean result. func (e *ValidationError) FlagOutput() *FlagOutput { return &FlagOutput{Valid: false} } // -- type OutputUnit struct { Valid bool `json:"valid"` KeywordLocation string `json:"keywordLocation"` AbsoluteKeywordLocation string `json:"AbsoluteKeywordLocation,omitempty"` InstanceLocation string `json:"instanceLocation"` Error *OutputError `json:"error,omitempty"` Errors []OutputUnit `json:"errors,omitempty"` } type OutputError struct { Kind ErrorKind p *message.Printer } func (k OutputError) MarshalJSON() ([]byte, error) { return json.Marshal(k.Kind.LocalizedString(k.p)) } // The `Basic` structure, a flat list of output units. func (e *ValidationError) BasicOutput() *OutputUnit { return e.LocalizedBasicOutput(defaultPrinter) } func (e *ValidationError) LocalizedBasicOutput(p *message.Printer) *OutputUnit { out := e.output(true, false, "", "", p) return &out } // The `Detailed` structure, based on the schema. func (e *ValidationError) DetailedOutput() *OutputUnit { return e.LocalizedDetailedOutput(defaultPrinter) } func (e *ValidationError) LocalizedDetailedOutput(p *message.Printer) *OutputUnit { out := e.output(false, false, "", "", p) return &out } func (e *ValidationError) output(flatten, inRef bool, schemaURL, kwLoc string, p *message.Printer) OutputUnit { if !inRef { if _, ok := e.ErrorKind.(*kind.Reference); ok { inRef = true } } if schemaURL != "" { kwLoc += e.SchemaURL[len(schemaURL):] if ref, ok := e.ErrorKind.(*kind.Reference); ok { kwLoc += jsonPtr(ref.KeywordPath()) } } schemaURL = e.schemaURL() keywordLocation := kwLoc if _, ok := e.ErrorKind.(*kind.Reference); !ok { keywordLocation += jsonPtr(e.ErrorKind.KeywordPath()) } out := OutputUnit{ Valid: false, InstanceLocation: jsonPtr(e.InstanceLocation), KeywordLocation: keywordLocation, } if inRef { out.AbsoluteKeywordLocation = e.absoluteKeywordLocation() } for _, cause := range e.Causes { causeOut := cause.output(flatten, inRef, schemaURL, kwLoc, p) if cause.skip() { causeOut = causeOut.Errors[0] } if flatten { errors := causeOut.Errors causeOut.Errors = nil causeOut.Error = &OutputError{cause.ErrorKind, p} out.Errors = append(out.Errors, causeOut) if len(errors) > 0 { out.Errors = append(out.Errors, errors...) } } else { out.Errors = append(out.Errors, causeOut) } } if len(out.Errors) == 0 { out.Error = &OutputError{e.ErrorKind, p} } return out }