Help us shape the future of Go! Take the 2026 Go Developer Survey

ginadapter

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

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

View Source
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.

View Source
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

func RegisterRoutes(router gin.IRoutes, options *Options) error

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

type Options struct {
	Engine           *livewire.Engine
	MaximumBodyBytes int64
}

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.

Jump to

Keyboard shortcuts

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