render

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 27, 2024 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package render contains implementations of lit.Response, suitable for responding requests.

JSON responses

For simple JSON responses, one can use the function JSON or their "shortcuts", such as OK, BadRequest or Unauthorized. All of them are constructors for JSONResponse.

204 No Content responses

In a special case, the constructor NoContent returns a NoContentResponse, that does not contain a body.

Redirections

The constructor Redirect can be used to redirect a request. If one wants more granularity, the specific redirection functions are also available, such as Found or PermanentRedirect.

Serving files

The constructor File can be used to serve files. It uses internally the http.ServeFile function.

Streaming

The constructor Stream can be used to serve streams. It uses internally the http.ServeContent function.

Custom responses

In order to create new responses not mapped in this package (or to use Facades), such as a YAML response or a new way of serving streams, one can:

Example (CustomResponse)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/jvcoutinho/lit"
	"github.com/jvcoutinho/lit/bind"
	"github.com/jvcoutinho/lit/render"
	"github.com/jvcoutinho/lit/validate"
	"gopkg.in/yaml.v3"
)

// YAMLResponse is a lit.Response that prints a YAML formatted-body as response. It sets
// the Content-Type header to "application/x-yaml".
//
// If the response contains a body but its marshalling fails, YAMLResponse responds an Internal Server Error
// with the error message as plain text.
type YAMLResponse struct {
	StatusCode int
	Header     http.Header
	Body       any
}

// YAML responds the request with statusCode and a body marshalled as YAML. Nil body equals empty body.
//
// If body is a string or an error, YAML marshals render.Message with the body assigned to render.Message.Value.
// Otherwise, it marshals the body as is.
func YAML(statusCode int, body any) YAMLResponse {
	switch cast := body.(type) {
	case string:
		return YAMLResponse{statusCode, make(http.Header), render.Message{Message: cast}}
	case error:
		return YAMLResponse{statusCode, make(http.Header), render.Message{Message: cast.Error()}}
	default:
		return YAMLResponse{statusCode, make(http.Header), cast}
	}
}

func (r YAMLResponse) Write(w http.ResponseWriter) {
	responseHeader := w.Header()
	for key := range r.Header {
		responseHeader.Set(key, r.Header.Get(key))
	}

	if r.Body == nil {
		w.WriteHeader(r.StatusCode)
		return
	}

	w.Header().Set("Content-Type", "application/x-yaml")

	if err := r.writeBody(w); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func (r YAMLResponse) writeBody(w http.ResponseWriter) error {
	bodyBytes, err := yaml.Marshal(r.Body)
	if err != nil {
		return err
	}

	w.WriteHeader(r.StatusCode)

	_, err = w.Write(bodyBytes)

	return err
}

type Request struct {
	A int `query:"a"`
	B int `query:"b"`
}

func (r *Request) Validate() []validate.Field {
	return []validate.Field{
		validate.NotEqual(&r.B, 0),
	}
}

func Divide(r *lit.Request) lit.Response {
	req, err := bind.Query[Request](r)
	if err != nil {
		return YAML(http.StatusBadRequest, err)
	}

	return YAML(http.StatusOK, req.A/req.B)
}

func main() {
	r := lit.NewRouter()
	r.GET("/div", Divide)

	res := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/div?a=4&b=2", nil)
	r.ServeHTTP(res, req)
	fmt.Println(res.Body)

	res = httptest.NewRecorder()
	req = httptest.NewRequest(http.MethodGet, "/div?a=2&b=0", nil)
	r.ServeHTTP(res, req)
	fmt.Println(res.Body)

}
Output:

2

message: b should not be equal to 0
Example (Redirecting)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/jvcoutinho/lit"
	"github.com/jvcoutinho/lit/render"
)

func DeprecatedHelloWorld(r *lit.Request) lit.Response {
	return render.PermanentRedirect(r, "/new")
}

func HelloWorld(_ *lit.Request) lit.Response {
	return render.OK("Hello, World!")
}

func main() {
	r := lit.NewRouter()
	r.GET("/", DeprecatedHelloWorld)
	r.GET("/new", HelloWorld)

	res := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	r.ServeHTTP(res, req)

	fmt.Println(res.Header(), res.Code)
	fmt.Println(res.Body)

}
Output:

map[Content-Type:[text/html; charset=utf-8] Location:[/new]] 308
<a href="/new">Permanent Redirect</a>.
Example (ServingFiles)
package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"
	"os"

	"github.com/jvcoutinho/lit"
	"github.com/jvcoutinho/lit/render"
)

var f *os.File

func ServeFile(r *lit.Request) lit.Response {
	return render.File(r, f.Name())
}

func main() {
	f = createTempFile()
	defer os.Remove(f.Name())

	r := lit.NewRouter()
	r.GET("/file", ServeFile)

	res := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/file", nil)
	r.ServeHTTP(res, req)

	fmt.Println(res.Body)

}

func createTempFile() *os.File {
	f, err := os.CreateTemp("", "test_file")
	if err != nil {
		log.Fatal(err)
	}

	if _, err := f.Write([]byte("content")); err != nil {
		log.Fatal(err)
	}

	if err := f.Close(); err != nil {
		log.Fatal(err)
	}

	return f
}
Output:

content
Example (Streaming)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/jvcoutinho/lit"
	"github.com/jvcoutinho/lit/render"
)

func Stream(r *lit.Request) lit.Response {
	streamContent := strings.NewReader("streaming content")

	return render.Stream(r, streamContent)
}

func main() {
	r := lit.NewRouter()
	r.GET("/stream", Stream)

	res := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/stream", nil)
	r.ServeHTTP(res, req)

	fmt.Println(res.Body)
	fmt.Println(res.Header())

	res = httptest.NewRecorder()
	req = httptest.NewRequest(http.MethodGet, "/stream", nil)
	req.Header.Set("If-Match", "tag")
	r.ServeHTTP(res, req)

	fmt.Println(res.Body)

}
Output:

streaming content
map[Accept-Ranges:[bytes] Content-Length:[17] Content-Type:[text/plain; charset=utf-8]]

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type FileResponse

type FileResponse struct {
	Request  *lit.Request
	FilePath string
}

FileResponse is a lit.Response that sends small chunks of data of a given file.

If the file does not exist, FileResponse responds the request with 404 Not Found.

func File

func File(r *lit.Request, path string) FileResponse

File responds the request with a stream of the contents of a file or directory in path (absolute or relative to the current directory).

If the file does not exist, File responds the request with 404 Not Found.

func (FileResponse) Write

func (r FileResponse) Write(w http.ResponseWriter)

type JSONResponse

type JSONResponse struct {
	StatusCode int
	Header     http.Header
	Body       any
}

JSONResponse is a lit.Response that prints a JSON formatted-body as response. It sets the Content-Type header to "application/json".

If the response contains a body but its marshalling fails, JSON responds an Internal Server Error with the error message as plain text.

func Accepted

func Accepted(body any) JSONResponse

Accepted responds the request with 202 Accepted and a body marshalled as JSON.

func BadRequest

func BadRequest(body any) JSONResponse

BadRequest responds the request with 400 Bad Request and a body marshalled as JSON.

func Conflict

func Conflict(body any) JSONResponse

Conflict responds the request with 409 Conflict and a body marshalled as JSON.

func Created

func Created(body any, locationURL string) JSONResponse

Created responds the request with 201 Created, a body marshalled as JSON and the URL of the created resource in the Location header.

func Forbidden

func Forbidden(body any) JSONResponse

Forbidden responds the request with 403 Forbidden and a body marshalled as JSON.

func InternalServerError

func InternalServerError(body any) JSONResponse

InternalServerError responds the request with 500 Internal Server Error and a body marshalled as JSON.

func JSON

func JSON(statusCode int, body any) JSONResponse

JSON responds the request with statusCode and a body marshalled as JSON. Nil body equals empty body.

If body is a string or an error, JSON marshals Message with the body assigned to Message.Value. Otherwise, it marshals the body as is.

func NotFound

func NotFound(body any) JSONResponse

NotFound responds the request with 404 Not Found and a body marshalled as JSON.

func OK

func OK(body any) JSONResponse

OK responds the request with 200 OK and a body marshalled as JSON.

func Unauthorized

func Unauthorized(body any) JSONResponse

Unauthorized responds the request with 401 Unauthorized and a body marshalled as JSON.

func UnprocessableContent

func UnprocessableContent(body any) JSONResponse

UnprocessableContent responds the request with 422 Unprocessable Content and a body marshalled as JSON.

func (JSONResponse) WithHeader

func (r JSONResponse) WithHeader(key, value string) JSONResponse

WithHeader sets the response header entries associated with key to value.

func (JSONResponse) Write

func (r JSONResponse) Write(w http.ResponseWriter)

type Message

type Message struct {
	// Content of the message.
	Message string `json:"message"`
}

Message is a standard response for strings and errors.

type NoContentResponse

type NoContentResponse struct {
	Header http.Header
}

NoContentResponse is a lit.Response without a body and status code 204 No Content.

func NoContent

func NoContent() NoContentResponse

NoContent responds the request with 204 No Content.

func (NoContentResponse) WithHeader

func (r NoContentResponse) WithHeader(key, value string) NoContentResponse

WithHeader sets the response header entries associated with key to value.

func (NoContentResponse) Write

type RedirectResponse

type RedirectResponse struct {
	StatusCode  int
	Request     *lit.Request
	LocationURL string
}

RedirectResponse is a lit.Response that performs redirects.

func Found

func Found(r *lit.Request, locationURL string) RedirectResponse

Found responds the request with 302 Found and a target URL (absolute or relative to the request path) in the Location header.

func MovedPermanently

func MovedPermanently(r *lit.Request, locationURL string) RedirectResponse

MovedPermanently responds the request with 301 Moved Permanently and a target URL (absolute or relative to the request path) in the Location header.

func PermanentRedirect

func PermanentRedirect(r *lit.Request, locationURL string) RedirectResponse

PermanentRedirect responds the request with [307 Permanent Redirect] and a target URL (absolute or relative to the request path) in the Location header.

func Redirect

func Redirect(r *lit.Request, locationURL string, permanent, preserveMethod bool) RedirectResponse

Redirect responds the request with a redirection status code and a target URL (absolute or relative to the request path) in the Location header.

func SeeOther

func SeeOther(r *lit.Request, locationURL string) RedirectResponse

SeeOther responds the request with 303 See Other and a target URL (absolute or relative to the request path) in the Location header.

func TemporaryRedirect

func TemporaryRedirect(r *lit.Request, locationURL string) RedirectResponse

TemporaryRedirect responds the request with 307 Temporary Redirect and a target URL (absolute or relative to the request path) in the Location header.

func (RedirectResponse) Write

type StreamResponse

type StreamResponse struct {
	Request      *lit.Request
	Content      io.ReadSeeker
	FilePath     string
	LastModified time.Time
}

StreamResponse is a lit.Response that sends separated small chunks of data from a given content.

func Stream

func Stream(r *lit.Request, content io.ReadSeeker) StreamResponse

Stream responds the request with a stream, sending smaller chunks of a possibly large data.

func (StreamResponse) WithFilePath

func (r StreamResponse) WithFilePath(filePath string) StreamResponse

WithFilePath sets the file path property of the stream. If it is set, StreamResponse uses its extension to derive the Content-Type header, falling back to the stream content otherwise or if it fails.

func (StreamResponse) WithLastModified

func (r StreamResponse) WithLastModified(lastModified time.Time) StreamResponse

WithLastModified sets the last modified property of the stream. If it is set, StreamResponse includes it in the Last-Modified header and, if the request contains an If-Modified-Since header, it uses its value to decide whether the content needs to be sent at all.

func (StreamResponse) Write

func (r StreamResponse) Write(w http.ResponseWriter)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL