...

Source file src/github.com/pelletier/go-toml/v2/errors.go

Documentation: github.com/pelletier/go-toml/v2

     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  }
   253  

View as plain text