gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

errors.go (5992B)


      1 package toml
      2 
      3 import (
      4 	"fmt"
      5 	"strconv"
      6 	"strings"
      7 
      8 	"github.com/pelletier/go-toml/v2/internal/danger"
      9 	"github.com/pelletier/go-toml/v2/unstable"
     10 )
     11 
     12 // DecodeError represents an error encountered during the parsing or decoding
     13 // of a TOML document.
     14 //
     15 // In addition to the error message, it contains the position in the document
     16 // where it happened, as well as a human-readable representation that shows
     17 // where the error occurred in the document.
     18 type DecodeError struct {
     19 	message string
     20 	line    int
     21 	column  int
     22 	key     Key
     23 
     24 	human string
     25 }
     26 
     27 // StrictMissingError occurs in a TOML document that does not have a
     28 // corresponding field in the target value. It contains all the missing fields
     29 // in Errors.
     30 //
     31 // Emitted by Decoder when DisallowUnknownFields() was called.
     32 type StrictMissingError struct {
     33 	// One error per field that could not be found.
     34 	Errors []DecodeError
     35 }
     36 
     37 // Error returns the canonical string for this error.
     38 func (s *StrictMissingError) Error() string {
     39 	return "strict mode: fields in the document are missing in the target struct"
     40 }
     41 
     42 // String returns a human readable description of all errors.
     43 func (s *StrictMissingError) String() string {
     44 	var buf strings.Builder
     45 
     46 	for i, e := range s.Errors {
     47 		if i > 0 {
     48 			buf.WriteString("\n---\n")
     49 		}
     50 
     51 		buf.WriteString(e.String())
     52 	}
     53 
     54 	return buf.String()
     55 }
     56 
     57 type Key []string
     58 
     59 // Error returns the error message contained in the DecodeError.
     60 func (e *DecodeError) Error() string {
     61 	return "toml: " + e.message
     62 }
     63 
     64 // String returns the human-readable contextualized error. This string is multi-line.
     65 func (e *DecodeError) String() string {
     66 	return e.human
     67 }
     68 
     69 // Position returns the (line, column) pair indicating where the error
     70 // occurred in the document. Positions are 1-indexed.
     71 func (e *DecodeError) Position() (row int, column int) {
     72 	return e.line, e.column
     73 }
     74 
     75 // Key that was being processed when the error occurred. The key is present only
     76 // if this DecodeError is part of a StrictMissingError.
     77 func (e *DecodeError) Key() Key {
     78 	return e.key
     79 }
     80 
     81 // decodeErrorFromHighlight creates a DecodeError referencing a highlighted
     82 // range of bytes from document.
     83 //
     84 // highlight needs to be a sub-slice of document, or this function panics.
     85 //
     86 // The function copies all bytes used in DecodeError, so that document and
     87 // highlight can be freely deallocated.
     88 //
     89 //nolint:funlen
     90 func wrapDecodeError(document []byte, de *unstable.ParserError) *DecodeError {
     91 	offset := danger.SubsliceOffset(document, de.Highlight)
     92 
     93 	errMessage := de.Error()
     94 	errLine, errColumn := positionAtEnd(document[:offset])
     95 	before, after := linesOfContext(document, de.Highlight, offset, 3)
     96 
     97 	var buf strings.Builder
     98 
     99 	maxLine := errLine + len(after) - 1
    100 	lineColumnWidth := len(strconv.Itoa(maxLine))
    101 
    102 	// Write the lines of context strictly before the error.
    103 	for i := len(before) - 1; i > 0; i-- {
    104 		line := errLine - i
    105 		buf.WriteString(formatLineNumber(line, lineColumnWidth))
    106 		buf.WriteString("|")
    107 
    108 		if len(before[i]) > 0 {
    109 			buf.WriteString(" ")
    110 			buf.Write(before[i])
    111 		}
    112 
    113 		buf.WriteRune('\n')
    114 	}
    115 
    116 	// Write the document line that contains the error.
    117 
    118 	buf.WriteString(formatLineNumber(errLine, lineColumnWidth))
    119 	buf.WriteString("| ")
    120 
    121 	if len(before) > 0 {
    122 		buf.Write(before[0])
    123 	}
    124 
    125 	buf.Write(de.Highlight)
    126 
    127 	if len(after) > 0 {
    128 		buf.Write(after[0])
    129 	}
    130 
    131 	buf.WriteRune('\n')
    132 
    133 	// Write the line with the error message itself (so it does not have a line
    134 	// number).
    135 
    136 	buf.WriteString(strings.Repeat(" ", lineColumnWidth))
    137 	buf.WriteString("| ")
    138 
    139 	if len(before) > 0 {
    140 		buf.WriteString(strings.Repeat(" ", len(before[0])))
    141 	}
    142 
    143 	buf.WriteString(strings.Repeat("~", len(de.Highlight)))
    144 
    145 	if len(errMessage) > 0 {
    146 		buf.WriteString(" ")
    147 		buf.WriteString(errMessage)
    148 	}
    149 
    150 	// Write the lines of context strictly after the error.
    151 
    152 	for i := 1; i < len(after); i++ {
    153 		buf.WriteRune('\n')
    154 		line := errLine + i
    155 		buf.WriteString(formatLineNumber(line, lineColumnWidth))
    156 		buf.WriteString("|")
    157 
    158 		if len(after[i]) > 0 {
    159 			buf.WriteString(" ")
    160 			buf.Write(after[i])
    161 		}
    162 	}
    163 
    164 	return &DecodeError{
    165 		message: errMessage,
    166 		line:    errLine,
    167 		column:  errColumn,
    168 		key:     de.Key,
    169 		human:   buf.String(),
    170 	}
    171 }
    172 
    173 func formatLineNumber(line int, width int) string {
    174 	format := "%" + strconv.Itoa(width) + "d"
    175 
    176 	return fmt.Sprintf(format, line)
    177 }
    178 
    179 func linesOfContext(document []byte, highlight []byte, offset int, linesAround int) ([][]byte, [][]byte) {
    180 	return beforeLines(document, offset, linesAround), afterLines(document, highlight, offset, linesAround)
    181 }
    182 
    183 func beforeLines(document []byte, offset int, linesAround int) [][]byte {
    184 	var beforeLines [][]byte
    185 
    186 	// Walk the document backward from the highlight to find previous lines
    187 	// of context.
    188 	rest := document[:offset]
    189 backward:
    190 	for o := len(rest) - 1; o >= 0 && len(beforeLines) <= linesAround && len(rest) > 0; {
    191 		switch {
    192 		case rest[o] == '\n':
    193 			// handle individual lines
    194 			beforeLines = append(beforeLines, rest[o+1:])
    195 			rest = rest[:o]
    196 			o = len(rest) - 1
    197 		case o == 0:
    198 			// add the first line only if it's non-empty
    199 			beforeLines = append(beforeLines, rest)
    200 
    201 			break backward
    202 		default:
    203 			o--
    204 		}
    205 	}
    206 
    207 	return beforeLines
    208 }
    209 
    210 func afterLines(document []byte, highlight []byte, offset int, linesAround int) [][]byte {
    211 	var afterLines [][]byte
    212 
    213 	// Walk the document forward from the highlight to find the following
    214 	// lines of context.
    215 	rest := document[offset+len(highlight):]
    216 forward:
    217 	for o := 0; o < len(rest) && len(afterLines) <= linesAround; {
    218 		switch {
    219 		case rest[o] == '\n':
    220 			// handle individual lines
    221 			afterLines = append(afterLines, rest[:o])
    222 			rest = rest[o+1:]
    223 			o = 0
    224 
    225 		case o == len(rest)-1:
    226 			// add last line only if it's non-empty
    227 			afterLines = append(afterLines, rest)
    228 
    229 			break forward
    230 		default:
    231 			o++
    232 		}
    233 	}
    234 
    235 	return afterLines
    236 }
    237 
    238 func positionAtEnd(b []byte) (row int, column int) {
    239 	row = 1
    240 	column = 1
    241 
    242 	for _, c := range b {
    243 		if c == '\n' {
    244 			row++
    245 			column = 1
    246 		} else {
    247 			column++
    248 		}
    249 	}
    250 
    251 	return
    252 }