github.com/jacobsoderblom/buffalo@v0.11.0/render/auto.go (about) 1 package render 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "reflect" 8 "regexp" 9 "strings" 10 11 "github.com/markbates/inflect" 12 "github.com/pkg/errors" 13 ) 14 15 var errNoID = errors.New("no ID on model") 16 17 // ErrRedirect indicates to Context#Render that this is a 18 // redirect and a template shouldn't be rendered. 19 type ErrRedirect struct { 20 Status int 21 URL string 22 } 23 24 func (ErrRedirect) Error() string { 25 return "" 26 } 27 28 // Auto figures out how to render the model based information 29 // about the request and the name of the model. Auto supports 30 // automatic rendering of HTML, JSON, and XML. Any status code 31 // give to Context#Render between 300 - 400 will be respected 32 // by Auto. Other status codes are not. 33 /* 34 # Rules for HTML template lookup: 35 GET /users - users/index.html 36 GET /users/id - users/show.html 37 GET /users/new - users/new.html 38 GET /users/id/edit - users/edit.html 39 POST /users - (redirect to /users/id or render user/new.html) 40 PUT /users/edit - (redirect to /users/id or render user/edit.html) 41 DELETE /users/id - redirect to /users 42 */ 43 func Auto(ctx context.Context, i interface{}) Renderer { 44 e := New(Options{}) 45 return e.Auto(ctx, i) 46 } 47 48 // Auto figures out how to render the model based information 49 // about the request and the name of the model. Auto supports 50 // automatic rendering of HTML, JSON, and XML. Any status code 51 // give to Context#Render between 300 - 400 will be respected 52 // by Auto. Other status codes are not. 53 /* 54 # Rules for HTML template lookup: 55 GET /users - users/index.html 56 GET /users/id - users/show.html 57 GET /users/new - users/new.html 58 GET /users/id/edit - users/edit.html 59 POST /users - (redirect to /users/id or render user/new.html) 60 PUT /users/edit - (redirect to /users/id or render user/edit.html) 61 DELETE /users/id - redirect to /users 62 */ 63 func (e *Engine) Auto(ctx context.Context, i interface{}) Renderer { 64 ct, ok := ctx.Value("contentType").(string) 65 if !ok { 66 ct = "text/html" 67 } 68 ct = strings.ToLower(ct) 69 70 if strings.Contains(ct, "json") { 71 return e.JSON(i) 72 } 73 74 if strings.Contains(ct, "xml") { 75 return e.XML(i) 76 } 77 78 return htmlAutoRenderer{ 79 Engine: e, 80 model: i, 81 } 82 } 83 84 type htmlAutoRenderer struct { 85 *Engine 86 model interface{} 87 } 88 89 func (htmlAutoRenderer) ContentType() string { 90 return "text/html" 91 } 92 93 func (ir htmlAutoRenderer) Render(w io.Writer, data Data) error { 94 name := inflect.Name(ir.typeName()) 95 name = inflect.Name(name.Singular()) 96 pname := inflect.Name(name.Plural()) 97 98 if ir.isPlural() { 99 data[pname.VarCasePlural()] = ir.model 100 } else { 101 data[name.VarCaseSingular()] = ir.model 102 } 103 104 switch data["method"] { 105 case "PUT", "POST": 106 if err := ir.redirect(pname, w, data); err != nil { 107 if er, ok := err.(ErrRedirect); ok && er.Status >= 300 && er.Status < 400 { 108 return err 109 } 110 if data["method"] == "PUT" { 111 return ir.HTML(fmt.Sprintf("%s/edit.html", pname.File())).Render(w, data) 112 } 113 return ir.HTML(fmt.Sprintf("%s/new.html", pname.File())).Render(w, data) 114 } 115 return nil 116 case "DELETE": 117 return ErrRedirect{ 118 Status: 302, 119 URL: "/" + pname.URL(), 120 } 121 } 122 if cp, ok := data["current_path"].(string); ok { 123 if strings.HasSuffix(cp, "/edit") { 124 return ir.HTML(fmt.Sprintf("%s/edit.html", pname.File())).Render(w, data) 125 } 126 if strings.HasSuffix(cp, "/new") { 127 return ir.HTML(fmt.Sprintf("%s/new.html", pname.File())).Render(w, data) 128 } 129 130 x, err := regexp.Compile(fmt.Sprintf("%s/.+", pname.URL())) 131 if err != nil { 132 return errors.WithStack(err) 133 } 134 if x.MatchString(cp) { 135 return ir.HTML(fmt.Sprintf("%s/show.html", pname.File())).Render(w, data) 136 } 137 } 138 139 return ir.HTML(fmt.Sprintf("%s/%s.html", pname.File(), "index")).Render(w, data) 140 } 141 142 func (ir htmlAutoRenderer) redirect(name inflect.Name, w io.Writer, data Data) error { 143 rv := reflect.Indirect(reflect.ValueOf(ir.model)) 144 f := rv.FieldByName("ID") 145 if !f.IsValid() { 146 return errNoID 147 } 148 149 fi := f.Interface() 150 rt := reflect.TypeOf(fi) 151 zero := reflect.Zero(rt) 152 if fi != zero.Interface() { 153 url := fmt.Sprintf("/%s/%v", name.URL(), f.Interface()) 154 155 code := 302 156 if i, ok := data["status"].(int); ok { 157 if i >= 300 { 158 code = i 159 } 160 } 161 return ErrRedirect{ 162 Status: code, 163 URL: url, 164 } 165 } 166 return errNoID 167 } 168 169 func (ir htmlAutoRenderer) typeName() string { 170 rv := reflect.Indirect(reflect.ValueOf(ir.model)) 171 rt := rv.Type() 172 switch rt.Kind() { 173 case reflect.Slice, reflect.Array: 174 el := rt.Elem() 175 return el.Name() 176 default: 177 return rt.Name() 178 } 179 } 180 181 func (ir htmlAutoRenderer) isPlural() bool { 182 rv := reflect.Indirect(reflect.ValueOf(ir.model)) 183 rt := rv.Type() 184 switch rt.Kind() { 185 case reflect.Slice, reflect.Array, reflect.Map: 186 return true 187 } 188 return false 189 }