github.com/fedir/buffalo@v0.11.1/render/auto.go~HEAD (about) 1 package render 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "path" 8 "reflect" 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().Singular()) 95 pname := inflect.Name(name.Plural()) 96 97 if ir.isPlural() { 98 data[pname.VarCasePlural()] = ir.model 99 } else { 100 data[name.VarCaseSingular()] = ir.model 101 } 102 103 cp, ok := data["current_path"].(string) 104 switch data["method"] { 105 case "PUT": 106 code := ir.status(data) 107 // if successful redirect to the GET version of the URL 108 // PUT /users/1 -> redirect -> GET /users/1 109 if code < 400 { 110 return ErrRedirect{ 111 Status: code, 112 URL: cp, 113 } 114 } 115 if ok { 116 // PUT /users/1 -> /users 117 cp = path.Dir(cp) 118 } else { 119 cp = pname.File() 120 } 121 return ir.HTML(fmt.Sprintf("%s/edit.html", cp)).Render(w, data) 122 case "POST": 123 if err := ir.redirect(cp, w, data); err != nil { 124 if er, ok := err.(ErrRedirect); ok && er.Status >= 300 && er.Status < 400 { 125 return err 126 } 127 } 128 return ir.HTML(fmt.Sprintf("%s/new.html", cp)).Render(w, data) 129 case "DELETE": 130 if ok { 131 // DELETE /users/{id} -> /users 132 cp = path.Dir(cp) 133 } else { 134 cp = "/" + pname.URL() 135 } 136 return ErrRedirect{ 137 Status: 302, 138 URL: cp, 139 } 140 } 141 if ok { 142 if strings.HasSuffix(cp, "/edit") { 143 // GET /users/{id}/edit -> /users 144 cp = path.Dir(path.Dir(cp)) 145 return ir.HTML(fmt.Sprintf("%s/edit.html", cp)).Render(w, data) 146 } 147 if strings.HasSuffix(cp, "/new") { 148 // GET /users/new -> /users 149 cp = path.Dir(cp) 150 return ir.HTML(fmt.Sprintf("%s/new.html", cp)).Render(w, data) 151 } 152 153 if ir.isPlural() { 154 // GET /users - if it's a slice/array render the index page 155 return ir.HTML(fmt.Sprintf("%s/%s.html", cp, "index")).Render(w, data) 156 } 157 // GET /users/{id} 158 return ir.HTML(fmt.Sprintf("%s/show.html", path.Dir(cp))).Render(w, data) 159 } 160 161 return errors.New("could not auto render this model, please render it manually") 162 } 163 164 func (ir htmlAutoRenderer) redirect(path string, w io.Writer, data Data) error { 165 rv := reflect.Indirect(reflect.ValueOf(ir.model)) 166 f := rv.FieldByName("ID") 167 if !f.IsValid() { 168 return errNoID 169 } 170 171 fi := f.Interface() 172 rt := reflect.TypeOf(fi) 173 zero := reflect.Zero(rt) 174 if fi != zero.Interface() { 175 url := fmt.Sprintf("%s/%v", path, f.Interface()) 176 177 return ErrRedirect{ 178 Status: ir.status(data), 179 URL: url, 180 } 181 } 182 return errNoID 183 } 184 185 func (ir htmlAutoRenderer) status(data Data) int { 186 if i, ok := data["status"].(int); ok { 187 if i >= 300 { 188 return i 189 } 190 } 191 return 302 192 } 193 194 func (ir htmlAutoRenderer) typeName() inflect.Name { 195 rv := reflect.Indirect(reflect.ValueOf(ir.model)) 196 rt := rv.Type() 197 switch rt.Kind() { 198 case reflect.Slice, reflect.Array: 199 el := rt.Elem() 200 return inflect.Name(el.Name()) 201 } 202 return inflect.Name(rt.Name()) 203 } 204 205 func (ir htmlAutoRenderer) isPlural() bool { 206 rv := reflect.Indirect(reflect.ValueOf(ir.model)) 207 rt := rv.Type() 208 switch rt.Kind() { 209 case reflect.Slice, reflect.Array, reflect.Map: 210 return true 211 } 212 return false 213 }