Files
buildkit/vendor/github.com/go-openapi/validate/path.go
T
Tonis Tiigi 7281e97ffb vendor: update deps to match moby
- github.com/Microsoft/hcsshim v0.15.0-rc.4 -> v0.15.0
- google.golang.org/grpc v1.84.0 -> v1.85.0
- golang.org/x/net v0.61.0 -> v0.62.0
- go.opentelemetry.io/otel v1.46.0 -> v1.47.0
- go.opentelemetry.io/contrib v0.71.0 -> v0.72.0
- github.com/prometheus/client_golang v1.24.1 -> v1.25.0
- github.com/prometheus/procfs v0.21.1 -> v0.22.0
- github.com/go-openapi/* to moby versions

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2026-11-04 00:44:46 -08:00

270 lines
7.5 KiB
Go

// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package validate
import (
"slices"
"strconv"
"strings"
"github.com/go-openapi/jsonpointer"
)
// pathSegments is the location of a validated value inside a document,
// held as an ordered list of unescaped JSON pointer reference tokens.
//
// Validators build a location by appending tokens as they descend into
// properties and array items, then render it only when they report an error.
// Keeping the tokens apart until then makes it possible to produce a
// valid [RFC 6901] JSON pointer: a token is escaped when it is rendered, and
// the separator can never be confused with a token that contains one.
//
// The zero value is the location of the document root.
//
// [RFC 6901]: https://datatracker.ietf.org/doc/html/rfc6901
type pathSegments []pathToken
// pathToken is one step of a location.
//
// A token addresses a member the way the document does, which is not always
// the way a reader recognizes it: an operation addresses its parameters by
// index, while a message is far more useful naming them. When the two differ,
// display carries the readable form and only the message uses it.
type pathToken struct {
token string
display string
// structural marks a token a document needs to address the value, but
// that messages have never shown: a "properties" between a schema and one
// of its members, say. It is part of the pointer and absent from the
// dotted form.
structural bool
// cosmetic is the converse: a token messages name but that the document
// does not address, such as a parameter too broken to be found by name in
// the array holding it. It is part of the dotted form and absent from the
// pointer, which then stops at the deepest node the document does contain.
cosmetic bool
}
// readable renders a token the way a message should spell it.
func (t pathToken) readable() string {
if t.display != "" {
return t.display
}
return t.token
}
// newPathSegments builds a location from a list of unescaped tokens.
func newPathSegments(tokens ...string) pathSegments {
if len(tokens) == 0 {
return nil
}
segments := make(pathSegments, len(tokens))
for i, token := range tokens {
segments[i] = pathToken{token: token}
}
return segments
}
// rootPath is the location of the document root.
func rootPath() pathSegments { return nil }
// String implements [fmt.Stringer] with the legacy dotted notation, so that a
// location interpolated into a message reads as it always has.
func (p pathSegments) String() string { return p.dotted() }
// child returns the location of a named member of the value at p.
//
// The receiver is never modified: sibling children may be derived from the
// same parent without aliasing one another.
func (p pathSegments) child(token string) pathSegments {
return p.appendToken(pathToken{token: token})
}
// childAs returns the location of a member the document addresses as token,
// which messages should spell as display instead.
func (p pathSegments) childAs(token, display string) pathSegments {
return p.appendToken(pathToken{token: token, display: display})
}
// structuralChild returns the location of a member a document addresses but
// messages do not name.
func (p pathSegments) structuralChild(token string) pathSegments {
return p.appendToken(pathToken{token: token, structural: true})
}
// cosmeticChild returns a location that messages spell as a member named token,
// while the pointer stays on p.
func (p pathSegments) cosmeticChild(token string) pathSegments {
return p.appendToken(pathToken{token: token, cosmetic: true})
}
func (p pathSegments) appendToken(token pathToken) pathSegments {
child := make(pathSegments, len(p)+1)
copy(child, p)
child[len(p)] = p.inherit(token)
return child
}
// inherit passes down what a parent token says about addressability: nothing
// below a token the document does not address is addressable either, so the
// pointer has to stop at the same place.
func (p pathSegments) inherit(token pathToken) pathToken {
if len(p) > 0 && p[len(p)-1].cosmetic {
token.cosmetic = true
}
return token
}
// children returns the location of a chain of named members below p.
func (p pathSegments) children(tokens ...string) pathSegments {
child := make(pathSegments, len(p)+len(tokens))
copy(child, p)
for i, token := range tokens {
child[len(p)+i] = child[:len(p)+i].inherit(pathToken{token: token})
}
return child
}
// item returns the location of the index'th element of the array at p.
func (p pathSegments) item(index int) pathSegments {
return p.child(strconv.Itoa(index))
}
// isEmpty tells if p locates the document root.
func (p pathSegments) isEmpty() bool { return len(p) == 0 }
// last returns the trailing meaningful token, or an empty string at the
// document root.
//
// Structural tokens are skipped: they say how a document addresses the value,
// not what the value is, and the callers here are asking the latter.
func (p pathSegments) last() string {
if token, ok := p.meaningfulAt(0); ok {
return token
}
return ""
}
// beforeLast returns the meaningful token before the trailing one, or an empty
// string when p holds fewer than two of them.
func (p pathSegments) beforeLast() string {
if token, ok := p.meaningfulAt(1); ok {
return token
}
return ""
}
// meaningfulAt returns the nth token from the end, counting only the tokens a
// message would show.
func (p pathSegments) meaningfulAt(n int) (string, bool) {
seen := 0
for _, token := range slices.Backward(p) {
if token.structural {
continue
}
if seen == n {
return token.token, true
}
seen++
}
return "", false
}
// trimIndexes returns p without its trailing array index tokens.
//
// It answers "what is this value inside of", disregarding how deep into an
// array it sits: the items of an example are still an example.
func (p pathSegments) trimIndexes() pathSegments {
end := len(p)
for end > 0 && isIndexToken(p[end-1].token) {
end--
}
return p[:end]
}
// isIndexToken tells if a token addresses an array element rather than a member.
func isIndexToken(token string) bool {
if token == "" {
return false
}
for _, r := range token {
if r < '0' || r > '9' {
return false
}
}
return true
}
// hasSuffix tells if p ends with the given sequence of tokens.
func (p pathSegments) hasSuffix(suffix pathSegments) bool {
if len(suffix) > len(p) {
return false
}
offset := len(p) - len(suffix)
for i, token := range suffix {
if p[offset+i].token != token.token {
return false
}
}
return true
}
// dotted renders the location in the legacy dot-separated notation, e.g.
// "definitions.Pet.friends.0.name".
//
// Tokens are emitted verbatim: a token containing a dot is indistinguishable
// from a separator. This notation is kept because it is what surfaces as the
// name of a validation error, and API consumers of go-swagger servers see it.
// Use [pathSegments.pointer] whenever the location needs to be unambiguous.
func (p pathSegments) dotted() string {
readable := make([]string, 0, len(p))
for _, token := range p {
if token.structural {
continue
}
readable = append(readable, token.readable())
}
return strings.Join(readable, ".")
}
// pointer renders the location as an RFC 6901 JSON pointer, e.g.
// "/definitions/Pet/friends/0/name". The document root renders as "".
func (p pathSegments) pointer() string {
if len(p) == 0 {
return ""
}
var w strings.Builder
for _, token := range p {
if token.cosmetic {
continue
}
w.WriteByte('/')
w.WriteString(jsonpointer.Escape(token.token))
}
return w.String()
}