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