github.com/haraldLmueller/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  }