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  }