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  }