errors.go 5.9 KB

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