Documentation
¶
Overview ¶
Package ginadapter serves a livewire engine on gin (D3): RegisterRoutes mounts the REST routes of every declaration and of the library itself, SocketHandler is the socket endpoint as a gin handler, and every REST outcome is recorded on the request's wide event (D4), which the wide-event-logger's gin middleware put into the request. It is the only package of the module that imports gin and wide-event-logger; it never logs and never emits an event of its own. Everything in it is safe for concurrent use.
Example ¶
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"github.com/gin-gonic/gin"
"github.com/govengers/auth"
"github.com/govengers/auth/authorization"
"github.com/govengers/livewire"
"github.com/govengers/livewire/ginadapter"
)
type greeting struct {
ID string `json:"id"`
Text string `json:"text"`
}
func main() {
registry, err := livewire.NewRegistry(&livewire.RegistryOptions{BasePath: "/api"})
if err != nil {
fmt.Println(err)
return
}
err = registry.Add(livewire.NewList("greetings", "/greetings", &livewire.ListOptions[greeting, livewire.NoParameters]{
Read: authorization.IsUser(),
Load: func(context.Context, *livewire.NoParameters) ([]greeting, error) {
return []greeting{{ID: "1", Text: "hello"}}, nil
},
}))
if err != nil {
fmt.Println(err)
return
}
people := &identities{people: map[string]*auth.Identity{"ada": {Provider: "example", Subject: "ada", Active: true}}}
engine, err := livewire.NewEngine(&livewire.EngineOptions{Registry: registry, IdentitySource: people, Transactor: &transactor{}, SameHostOnly: true})
if err != nil {
fmt.Println(err)
return
}
defer func() { _ = engine.Close(context.Background()) }() // nothing is open; Close only ends the engine
gin.SetMode(gin.ReleaseMode)
router := gin.New()
router.Use(func(ginContext *gin.Context) { // stands in for the application's authentication
identity := people.get(ginContext.GetHeader(subjectHeader))
ginContext.Request = ginContext.Request.WithContext(auth.WithIdentity(ginContext.Request.Context(), identity))
})
if err := ginadapter.RegisterRoutes(router, &ginadapter.Options{Engine: engine}); err != nil {
fmt.Println(err)
return
}
router.GET("/api/socket", ginadapter.SocketHandler(engine))
request := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/api/greetings", nil)
request.Header.Set(subjectHeader, "ada")
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
fmt.Println(recorder.Code, recorder.Body.String())
}
Output: 200 {"limit":1,"offset":0,"total":1,"items":[{"id":"1","text":"hello"}]}
Index ¶
Examples ¶
Constants ¶
const ExportRowsTrailer = "Livewire-Export-Rows"
ExportRowsTrailer is the trailer of a complete export: the rows it wrote. The response declares it with its headers and sets it only when the export finished: a signal on top of the aborted body for a client that reads trailers, and the only one behind a writer wrapper without Unwrap.
const IdempotencyKeyHeader = "Idempotency-Key"
IdempotencyKeyHeader is the request header that carries a mutation's Idempotency-Key (D16).
Variables ¶
This section is empty.
Functions ¶
func RegisterRoutes ¶
RegisterRoutes mounts every REST route of the engine's registry on router, below the registry's BasePath, with `{name}` placeholders as gin's `:name`:
- a resource: GET Path (list), ItemPath, DistinctPath, HistoryPath, PresencePath and ExportPath as far as it declares them, PresencePath only when the engine serves presence;
- a list: GET Path; an object: GET Path and, with presence, PresencePath;
- a mutation: its method and Path; an upload: POST Path;
- the library's own: POST BatchPath, POST and GET FiltersPath, DELETE FiltersPath/:id.
Every read is an Engine.Read of the ReadRequest the socket's query sends for the same target (D46), every write an Engine.Call, Batch or Upload, so a REST body is byte for byte the socket's payload (D23). A mutation and an upload run their static Check before the body is read (D8): a refused request reads no body and is answered by the engine, which runs the same Check first and reports the outcome. Mutations, batches and uploads (once the file is read) run on a context the client's disconnect does not cancel, so a write that started finishes, and is audited, as what it is. The Idempotency-Key header is the call's key (D16), the X-Request-ID the wide-event middleware answers with is its request id (D31). Every refusal and failure is answered with livewire.ResponseFor's status and body. Each request's outcome is recorded on the request's wide event (D4): livewire.target, livewire.kind, livewire.read for a read kind, livewire.status, livewire.code on a refusal, livewire.rows for an export, and the event's success or failure.
It takes its dependencies and settings in one Options value, validated and copied (DES-001); it is a function, not a component, because it builds nothing that outlives the call but the handlers, which close over the copy. A route gin refuses (one that conflicts with a route already on router) is returned as the error, with the routes registered before it left in place; register on a router of the API's own.
func SocketHandler ¶
func SocketHandler(engine *livewire.Engine) gin.HandlerFunc
SocketHandler is the engine's socket endpoint as a gin handler; mount it behind the same authentication as the REST routes. The engine must not be nil.
Types ¶
type Options ¶
Options configures RegisterRoutes. Engine is required: every route runs through its read and write pipelines, and its registry names the routes. MaximumBodyBytes bounds the JSON body of a mutation, a batch and a stored filter (default 1 MiB, not negative); a larger one is 400 invalid_request at "body". An upload is bounded by its declared MaximumBytes.