github.com/franciscocpg/up@v0.1.10/internal/errorpage/errorpage.go (about) 1 // Package errorpage provides error page loading utilities. 2 package errorpage 3 4 import ( 5 "bytes" 6 "html/template" 7 "io/ioutil" 8 "path/filepath" 9 "sort" 10 "strconv" 11 "strings" 12 13 "github.com/pkg/errors" 14 ) 15 16 // Page is a single .html file matching 17 // one or more status codes. 18 type Page struct { 19 Name string 20 Code int 21 Range bool 22 Template *template.Template 23 } 24 25 // Match returns true if the page matches code. 26 func (p *Page) Match(code int) bool { 27 switch { 28 case p.Code == code: 29 return true 30 case p.Range && p.Code == code/100: 31 return true 32 case p.Name == "error" && code >= 400: 33 return true 34 case p.Name == "default" && code >= 400: 35 return true 36 default: 37 return false 38 } 39 } 40 41 // Specificity returns the specificity, where higher is more precise. 42 func (p *Page) Specificity() int { 43 switch { 44 case p.Name == "default": 45 return 4 46 case p.Name == "error": 47 return 3 48 case p.Range: 49 return 2 50 default: 51 return 1 52 } 53 } 54 55 // Render the page. 56 func (p *Page) Render(data interface{}) (string, error) { 57 var buf bytes.Buffer 58 59 if err := p.Template.Execute(&buf, data); err != nil { 60 return "", err 61 } 62 63 return buf.String(), nil 64 } 65 66 // Pages is a group of .html files 67 // matching one or more status codes. 68 type Pages []Page 69 70 // Match returns the matching page. 71 func (p Pages) Match(code int) *Page { 72 for _, page := range p { 73 if page.Match(code) { 74 return &page 75 } 76 } 77 78 return nil 79 } 80 81 // Load pages in dir. 82 func Load(dir string) (pages Pages, err error) { 83 files, err := ioutil.ReadDir(dir) 84 if err != nil { 85 return nil, errors.Wrap(err, "reading dir") 86 } 87 88 for _, file := range files { 89 if !isErrorPage(file.Name()) { 90 continue 91 } 92 93 path := filepath.Join(dir, file.Name()) 94 95 t, err := template.New(file.Name()).ParseFiles(path) 96 if err != nil { 97 return nil, errors.Wrap(err, "parsing template") 98 } 99 100 name := stripExt(file.Name()) 101 code, _ := strconv.Atoi(name) 102 103 if isRange(name) { 104 code = int(name[0] - '0') 105 } 106 107 page := Page{ 108 Name: name, 109 Code: code, 110 Range: isRange(name), 111 Template: t, 112 } 113 114 pages = append(pages, page) 115 } 116 117 pages = append(pages, Page{ 118 Name: "default", 119 Template: defaultPage, 120 }) 121 122 Sort(pages) 123 return 124 } 125 126 // Sort pages by specificity. 127 func Sort(pages Pages) { 128 sort.Slice(pages, func(i int, j int) bool { 129 a := pages[i] 130 b := pages[j] 131 return a.Specificity() < b.Specificity() 132 }) 133 } 134 135 // isErrorPage returns true if it looks like an error page. 136 func isErrorPage(path string) bool { 137 if filepath.Ext(path) != ".html" { 138 return false 139 } 140 141 name := stripExt(path) 142 143 if name == "error" { 144 return true 145 } 146 147 if isRange(name) { 148 return true 149 } 150 151 _, err := strconv.Atoi(name) 152 return err == nil 153 } 154 155 // isRange returns true if the name matches xx.s 156 func isRange(name string) bool { 157 return strings.HasSuffix(name, "xx") 158 } 159 160 // stripExt returns path without extname. 161 func stripExt(path string) string { 162 return strings.Replace(path, filepath.Ext(path), "", 1) 163 }