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  }