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