Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / errors.go
1 // Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
2 // Revel Framework source code and usage is governed by a MIT style
3 // license that can be found in the LICENSE file.
4
5 package revel
6
7 import (
8         "fmt"
9         "path/filepath"
10         "runtime/debug"
11         "strconv"
12         "strings"
13 )
14
15 // Error description, used as an argument to the error template.
16 type Error struct {
17         SourceType               string   // The type of source that failed to build.
18         Title, Path, Description string   // Description of the error, as presented to the user.
19         Line, Column             int      // Where the error was encountered.
20         SourceLines              []string // The entire source file, split into lines.
21         Stack                    string   // The raw stack trace string from debug.Stack().
22         MetaError                string   // Error that occurred producing the error page.
23         Link                     string   // A configurable link to wrap the error source in
24 }
25
26 // SourceLine structure to hold the per-source-line details.
27 type SourceLine struct {
28         Source  string
29         Line    int
30         IsError bool
31 }
32
33 // NewErrorFromPanic method finds the deepest stack from in user code and
34 // provide a code listing of that, on the line that eventually triggered
35 // the panic.  Returns nil if no relevant stack frame can be found.
36 func NewErrorFromPanic(err interface{}) *Error {
37
38         // Parse the filename and line from the originating line of app code.
39         // /Users/robfig/code/gocode/src/revel/examples/booking/app/controllers/hotels.go:191 (0x44735)
40         stack := string(debug.Stack())
41         frame, basePath := findRelevantStackFrame(stack)
42         if frame == -1 {
43                 return nil
44         }
45
46         stack = stack[frame:]
47         stackElement := stack[:strings.Index(stack, "\n")]
48         colonIndex := strings.LastIndex(stackElement, ":")
49         filename := stackElement[:colonIndex]
50         var line int
51         fmt.Sscan(stackElement[colonIndex+1:], &line)
52
53         // Show an error page.
54         description := "Unspecified error"
55         if err != nil {
56                 description = fmt.Sprint(err)
57         }
58         lines, readErr := ReadLines(filename)
59         if readErr != nil {
60                 utilLog.Error("Unable to read file", "file", filename, "error", readErr)
61         }
62         return &Error{
63                 Title:       "Runtime Panic",
64                 Path:        filename[len(basePath):],
65                 Line:        line,
66                 Description: description,
67                 SourceLines: lines,
68                 Stack:       stack,
69         }
70 }
71
72 // Error method constructs a plaintext version of the error, taking
73 // account that fields are optionally set. Returns e.g. Compilation Error
74 // (in views/header.html:51): expected right delim in end; got "}"
75 func (e *Error) Error() string {
76         loc := ""
77         if e.Path != "" {
78                 line := ""
79                 if e.Line != 0 {
80                         line = fmt.Sprintf(":%d", e.Line)
81                 }
82                 loc = fmt.Sprintf("(in %s%s)", e.Path, line)
83         }
84         header := loc
85         if e.Title != "" {
86                 if loc != "" {
87                         header = fmt.Sprintf("%s %s: ", e.Title, loc)
88                 } else {
89                         header = fmt.Sprintf("%s: ", e.Title)
90                 }
91         }
92         return fmt.Sprintf("%s%s Stack: %s", header, e.Description, e.Stack)
93 }
94
95 // ContextSource method returns a snippet of the source around
96 // where the error occurred.
97 func (e *Error) ContextSource() []SourceLine {
98         if e.SourceLines == nil {
99                 return nil
100         }
101         start := (e.Line - 1) - 5
102         if start < 0 {
103                 start = 0
104         }
105         end := (e.Line - 1) + 5
106         if end > len(e.SourceLines) {
107                 end = len(e.SourceLines)
108         }
109
110         lines := make([]SourceLine, end-start)
111         for i, src := range e.SourceLines[start:end] {
112                 fileLine := start + i + 1
113                 lines[i] = SourceLine{src, fileLine, fileLine == e.Line}
114         }
115         return lines
116 }
117
118 // SetLink method prepares a link and assign to Error.Link attribute
119 func (e *Error) SetLink(errorLink string) {
120         errorLink = strings.Replace(errorLink, "{{Path}}", e.Path, -1)
121         errorLink = strings.Replace(errorLink, "{{Line}}", strconv.Itoa(e.Line), -1)
122
123         e.Link = "<a href=" + errorLink + ">" + e.Path + ":" + strconv.Itoa(e.Line) + "</a>"
124 }
125
126 // Return the character index of the first relevant stack frame, or -1 if none were found.
127 // Additionally it returns the base path of the tree in which the identified code resides.
128 func findRelevantStackFrame(stack string) (int, string) {
129         // Find first item in SourcePath that isn't in RevelPath.
130         // If first item is in RevelPath, keep track of position, trim and check again.
131         partialStack := stack
132         sourcePath := filepath.ToSlash(SourcePath)
133         revelPath := filepath.ToSlash(RevelPath)
134         sumFrame := 0
135         for {
136                 frame := strings.Index(partialStack, sourcePath)
137                 revelFrame := strings.Index(partialStack, revelPath)
138
139                 if frame == -1 {
140                         break
141                 } else if frame != revelFrame {
142                         return sumFrame + frame, SourcePath
143                 } else {
144                         // Need to at least trim off the first character so this frame isn't caught again.
145                         partialStack = partialStack[frame+1:]
146                         sumFrame += frame + 1
147                 }
148         }
149         for _, module := range Modules {
150                 if frame := strings.Index(stack, filepath.ToSlash(module.Path)); frame != -1 {
151                         return frame, module.Path
152                 }
153         }
154         return -1, ""
155 }