// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build goexperiment.jsonv2

// Package jsonwire implements stateless functionality for handling JSON text.
package jsonwire import ( ) // TrimSuffixWhitespace trims JSON whitespace from the end of b. func ( []byte) []byte { // NOTE: The arguments and logic are kept simple to keep this inlinable. := len() - 1 for >= 0 && ([] == ' ' || [] == '\t' || [] == '\r' || [] == '\n') { -- } return [:+1] } // TrimSuffixString trims a valid JSON string at the end of b. // The behavior is undefined if there is not a valid JSON string present. func ( []byte) []byte { // NOTE: The arguments and logic are kept simple to keep this inlinable. if len() > 0 && [len()-1] == '"' { = [:len()-1] } for len() >= 2 && !([len()-1] == '"' && [len()-2] != '\\') { = [:len()-1] // trim all characters except an unescaped quote } if len() > 0 && [len()-1] == '"' { = [:len()-1] } return } // HasSuffixByte reports whether b ends with c. func ( []byte, byte) bool { // NOTE: The arguments and logic are kept simple to keep this inlinable. return len() > 0 && [len()-1] == } // TrimSuffixByte removes c from the end of b if it is present. func ( []byte, byte) []byte { // NOTE: The arguments and logic are kept simple to keep this inlinable. if len() > 0 && [len()-1] == { return [:len()-1] } return } // QuoteRune quotes the first rune in the input. func ( []byte) string { , := utf8.DecodeRune() if == utf8.RuneError && == 1 { return `'\x` + strconv.FormatUint(uint64([0]), 16) + `'` } return strconv.QuoteRune() } // CompareUTF16 lexicographically compares x to y according // to the UTF-16 codepoints of the UTF-8 encoded input strings. // This implements the ordering specified in RFC 8785, section 3.2.3. func (, []byte) int { // NOTE: This is an optimized, mostly allocation-free implementation // of CompareUTF16Simple in wire_test.go. FuzzCompareUTF16 verifies that the // two implementations agree on the result of comparing any two strings. := func( rune) bool { return ('\u0000' <= && <= '\uD7FF') || ('\uE000' <= && <= '\uFFFF') } for { if len() == 0 || len() == 0 { return cmp.Compare(len(), len()) } // ASCII fast-path. if [0] < utf8.RuneSelf || [0] < utf8.RuneSelf { if [0] != [0] { return cmp.Compare([0], [0]) } , = [1:], [1:] continue } // Decode next pair of runes as UTF-8. , := utf8.DecodeRune() , := utf8.DecodeRune() := () := () switch { // The x rune is a single UTF-16 codepoint, while // the y rune is a surrogate pair of UTF-16 codepoints. case && !: , _ = utf16.EncodeRune() // The y rune is a single UTF-16 codepoint, while // the x rune is a surrogate pair of UTF-16 codepoints. case && !: , _ = utf16.EncodeRune() } if != { return cmp.Compare(, ) } // Check for invalid UTF-8, in which case, // we just perform a byte-for-byte comparison. if isInvalidUTF8(, ) || isInvalidUTF8(, ) { if [0] != [0] { return cmp.Compare([0], [0]) } } , = [:], [:] } } // TODO(https://go.dev/issue/70547): Use utf8.ErrInvalid instead. var ErrInvalidUTF8 = errors.New("invalid UTF-8") func [ ~[]byte | ~string]( , string) error { , := utf8.DecodeRune([]byte()) return &InvalidTextError{"character", string([:]), } } func [ ~[]byte | ~string]( ) error { if len() > 6 { return &InvalidTextError{"surrogate pair", string(), "in string"} } return &InvalidTextError{"escape sequence", string(), "in string"} } type InvalidTextError struct { Label string // e.g., "character" | "escape sequence" | "surrogate pair" What string // raw invalid text Where string // e.g., "in string" | "at start of value" } func ( *InvalidTextError) () string { var string := strings.ContainsFunc(.What, func( rune) bool { return == '`' || == utf8.RuneError || unicode.IsSpace() || !unicode.IsPrint() }) switch { case utf8.RuneCount([]byte(.What)) == 1: = QuoteRune([]byte(.What)) case : = strconv.Quote(.What) default: = "`" + .What + "`" } return strings.TrimSuffix("invalid "+.Label+" "++" "+.Where, " ") } // TruncatePointer optionally truncates the JSON pointer, // enforcing that the length roughly does not exceed n. func ( string, int) string { if len() <= { return } := / 2 := len() - /2 // Avoid truncating a name if there are multiple names present. if := strings.LastIndexByte([:], '/'); > 0 { = } if := strings.IndexByte([:], '/'); >= 0 { += + len("/") } // Avoid truncation in the middle of a UTF-8 rune. for > 0 && isInvalidUTF8(utf8.DecodeLastRuneInString([:])) { -- } for < len() && isInvalidUTF8(utf8.DecodeRuneInString([:])) { ++ } // Determine the right middle fragment to use. var string switch strings.Count([:], "/") { case 0: = "…" case 1: = "…/…" default: = "…/…/…" } if strings.HasPrefix([:], "/") && != "…" { = strings.TrimPrefix(, "…") } if strings.HasSuffix([:], "/") && != "…" { = strings.TrimSuffix(, "…") } return [:] + + [:] } func ( rune, int) bool { return == utf8.RuneError && == 1 }