fuego

package module
v0.13.4 Latest Latest
Warning

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

Go to latest
Published: Mar 28, 2024 License: MIT Imports: 32 Imported by: 15

README

Fuego Logo

Fuego 🔥

Go Reference Go Report Card Coverage Status Discord Gophers

The framework for busy Go developers

Build your API or web application in minutes!

Go framework generating OpenAPI documentation from code. Inspired by Nest, built for Go developers.

Also empowers html/template, a-h/templ and maragudk/gomponents: see the example - actually running in prod!

Why Fuego?

Chi, Gin, Fiber and Echo are great frameworks. But since they were designed a long time ago, their current API does not allow them to deduce OpenAPI types from signatures, things that are now possible with generics. Fuego offers a lot of "modern Go based" features that make it easy to develop APIs and web applications.

Features

  • OpenAPI: Fuego automatically generates OpenAPI documentation from code - not from comments nor yaml files!
  • net/http compatible: Fuego is built on top of net/http, so you can use any http.Handler middleware or handler! Fuego also supports log/slog, context and html/template
  • Routing: Fuego provides a simple API for the Go 1.22 net/http
  • Serialization/Deserialization: Fuego automatically serializes and deserializes JSON, XML and HTML Forms based on user-provided structs (or not, if you want to do it yourself)
  • Validation: Fuego provides a simple and fast validator based on go-playground/validator
  • Transformation: easily transform your data by implementing the fuego.InTransform and fuego.OutTransform interfaces - also useful for validation
  • Middlewares: easily add a custom net/http middleware or use the provided middlewares.
  • Error handling: Fuego provides centralized error handling with the standard RFC 9457.
  • Rendering: Fuego provides a simple and fast rendering system based on html/template - you can still also use your own template system like templ or gomponents

Examples

Hello World
package main

import "github.com/go-fuego/fuego"

func main() {
	s := fuego.NewServer()

	fuego.Get(s, "/", func(c fuego.ContextNoBody) (string, error) {
		return "Hello, World!", nil
	})

	s.Run()
}
Simple POST
package main

import "github.com/go-fuego/fuego"

type MyInput struct {
	Name string `json:"name" validate:"required"`
}

type MyOutput struct {
	Message string `json:"message"`
}

func main() {
	s := fuego.NewServer()

	// Automatically generates OpenAPI documentation for this route
	fuego.Post(s, "/", func(c *fuego.ContextWithBody[MyInput]) (MyOutput, error) {
		body, err := c.Body()
		if err != nil {
			return MyOutput{}, err
		}

		return MyOutput{
			Message: "Hello, " + body.Name,
		}, nil
	})

	s.Run()
}
With transformation & custom validation
type MyInput struct {
	Name string `json:"name" validate:"required"`
}

// Will be called just before returning c.Body()
func (r *MyInput) InTransform(context.Context) error {
	r.Name = strings.ToLower(r.Name)

	if r.Name == "fuego" {
		return errors.New("fuego is not a valid name for this input")
	}

	return nil
}

More OpenAPI documentation
package main

import "github.com/go-fuego/fuego"

func main() {
	s := fuego.NewServer()

	// Custom OpenAPI options that cannot be deduced by the controller signature
	fuego.Post(s, "/", myController).
		Description("This route does something").
		Summary("This is my summary").
		Tags("MyTag"). // A tag is set by default according to the return type (can be desactivated)
		Deprecated()

	s.Run()
}
Std lib compatibility
package main

import (
	"net/http"
	"github.com/go-fuego/fuego"
)

func main() {
	s := fuego.NewServer()

	// Standard net/http middleware
	fuego.Use(s, func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("X-Hello", "World")
			next.ServeHTTP(w, r)
		})
	})

	// Standard net/http handler with automatic OpenAPI route declaration
	fuego.GetStd(s, "/std", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello, World!"))
	})

	s.Run()
}
All features
package main

import (
	"context"
	"errors"
	"net/http"
	"strings"

	chiMiddleware "github.com/go-chi/chi/v5/middleware"
	"github.com/go-fuego/fuego"
	"github.com/rs/cors"
)

type Received struct {
	Name string `json:"name" validate:"required"`
}

type MyResponse struct {
	Message       string `json:"message"`
	BestFramework string `json:"best"`
}

func main() {
	s := fuego.NewServer(
		fuego.WithAddr("localhost:8088"),
	)

	fuego.Use(s, cors.Default().Handler)
	fuego.Use(s, chiMiddleware.Compress(5, "text/html", "text/css"))

	// Fuego 🔥 handler with automatic OpenAPI generation, validation, (de)serialization and error handling
	fuego.Post(s, "/", func(c *fuego.ContextWithBody[Received]) (MyResponse, error) {
		data, err := c.Body()
		if err != nil {
			return MyResponse{}, err
		}

		c.Response().Header().Set("X-Hello", "World")

		return MyResponse{
			Message:       "Hello, " + data.Name,
			BestFramework: "Fuego!",
		}, nil
	})

	// Standard net/http handler with automatic OpenAPI route declaration
	fuego.GetStd(s, "/std", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello, World!"))
	})

	s.Run()
}

// InTransform will be called when using c.Body().
// It can be used to transform the entity and raise custom errors
func (r *Received) InTransform(context.Context) error {
	r.Name = strings.ToLower(r.Name)
	if r.Name == "fuego" {
		return errors.New("fuego is not a name")
	}
	return nil
}

// OutTransform will be called before sending data
func (r *MyResponse) OutTransform(context.Context) error {
	r.Message = strings.ToUpper(r.Message)
	return nil
}
curl  http://localhost:8088/std
# Hello, World!
curl http://localhost:8088 -X POST -d '{"name": "Your Name"}' -H 'Content-Type: application/json'
# {"message":"HELLO, YOUR NAME","best":"Fuego!"}
curl http://localhost:8088 -X POST -d '{"name": "Fuego"}' -H 'Content-Type: application/json'
# {"error":"cannot transform request body: cannot transform request body: fuego is not a name"}

From net/http to Fuego in 10s

https://github.com/go-fuego/fuego/assets/46993939/7438a71c-75a4-4e88-a584-71da6362c575

Views
Before
image
After
image
Diff
image
Benefits of using Fuego views (controllers returning HTML)
  • Never forget to return after an error
  • OpenAPI schema generated, listing all the routes
  • Deserialization and validation are more easy
  • Transition to Fuego is easy and fast

Contributing

See the contributing guide. Thanks to everyone who has contributed to this project! ❤️

Roadmap

See the board.

Disclaimer for experienced gophers

I know you might prefer to use net/http directly, but if having a frame can convince my company to use Go instead of Node, I'm happy to use it.

License

MIT

Documentation

Index

Examples

Constants

View Source
const JWTCookieName = "jwt_token"

Variables

View Source
var (
	ErrUnauthorized     = errors.New("unauthorized")
	ErrTokenNotFound    = errors.New("token not found")
	ErrInvalidTokenType = errors.New("invalid token type")
	ErrInvalidRolesType = errors.New("invalid role type. Must be []string")
	ErrExpired          = errors.New("token is expired")
)
View Source
var ReadOptions = readOptions{
	DisallowUnknownFields: true,
	MaxBodySize:           maxBodySize,
}

Functions

func AuthWall

func AuthWall(authorizedRoles ...string) func(next http.Handler) http.Handler

AuthWall is a middleware that checks if the user is authorized. If not, it returns an error. If authorized roles are provided, the user must have at least one of its role in the list. For example:

AuthWall("admin", "chef") // Will block a user with the "waiter" role and allow a user with a role "chef".
AuthWall("chef") // Will block a user with the "admin" and "client" role.
AuthWall() // Will block all users. To simply Check if the user is authenticated, use the [TokenToContext] middleware.

See the tests for more examples.

func AuthWallRegex

func AuthWallRegex(acceptedRolesRegex string) func(next http.Handler) http.Handler

AuthWallRegex is a middleware that checks if the user is authorized. If not, it returns an error. If authorized roles are provided, the user must have at least one of its role in the list that matches the regex. For example:

AuthWallRegex(`^(super)?admin$`) // Will block a user with the "waiter" role and allow a user with a role "admin".

See the tests for more examples.

func AuthWallRegexp

func AuthWallRegexp(acceptedRolesRegex *regexp.Regexp) func(next http.Handler) http.Handler

AuthWallRegexp is a middleware that checks if the user is authorized. If not, it returns an error. If authorized roles are provided, the user must have at least one of its role in the list that matches the regex. For example:

myRegexRule := regexp.MustCompile(`^(super)?admin$`)
AuthWallRegex(myRegexRule) // Will block a user with the "waiter" role and allow a user with a role "admin".

See the tests for more examples.

func DefaultOpenAPIHandler added in v0.13.0

func DefaultOpenAPIHandler(specURL string) http.Handler

func ErrorHandler

func ErrorHandler(err error) error

ErrorHandler is the default error handler used by the framework. It transforms any error into the unified error type HTTPError, Using the ErrorWithStatus and [ErrorWithInfo] interfaces.

func GetToken

func GetToken[T any](ctx context.Context) (T, error)

GetToken returns the validated token from the context, if found. To check if the user is authorized, use the AuthWall middleware, or create your own middleware. Example:

token, err := fuego.GetToken[MyCustomTokenType](ctx.Context())

func Markdown

func Markdown(content string) template.HTML

Markdown converts a markdown string to HTML.

func NewOpenApiSpec

func NewOpenApiSpec() openapi3.T

func ReadJSON

func ReadJSON[B any](context context.Context, input io.Reader) (B, error)

ReadJSON reads the request body as JSON. Can be used independantly from Fuego framework. Customisable by modifying ReadOptions.

func ReadString

func ReadString[B ~string](context context.Context, input io.Reader) (B, error)

ReadString reads the request body as string. Can be used independantly from Fuego framework. Customisable by modifying ReadOptions.

func ReadURLEncoded

func ReadURLEncoded[B any](r *http.Request) (B, error)

ReadURLEncoded reads the request body as HTML Form.

func ReadXML added in v0.13.2

func ReadXML[B any](context context.Context, input io.Reader) (B, error)

ReadXML reads the request body as XML. Can be used independantly from Fuego framework. Customisable by modifying ReadOptions.

func ReadYAML added in v0.13.0

func ReadYAML[B any](context context.Context, input io.Reader) (B, error)

ReadYAML reads the request body as YAML. Can be used independantly from Fuego framework. Customisable by modifying ReadOptions.

func RegisterOpenAPIOperation

func RegisterOpenAPIOperation[T any, B any](s *Server, method, path string) (*openapi3.Operation, error)

RegisterOpenAPIOperation registers an OpenAPI operation.

func Send

func Send(w http.ResponseWriter, text string)

Send sends a string response.

func SendJSON

func SendJSON(w http.ResponseWriter, ans any)

SendJSON sends a JSON response.

func SendJSONError

func SendJSONError(w http.ResponseWriter, err error)

SendJSONError sends a JSON error response. If the error implements ErrorWithStatus, the status code will be set.

func SendXML

func SendXML(w http.ResponseWriter, ans any)

SendXML sends a XML response.

func SendXMLError

func SendXMLError(w http.ResponseWriter, err error)

SendXMLError sends a XML error response. If the error implements ErrorWithStatus, the status code will be set.

func TokenFromContext

func TokenFromContext(ctx context.Context) (jwt.Claims, error)

TokenFromContext returns the validated token from the context, if found. To check if the user is authorized, use the AuthWall middleware, or create your own middleware. Even though it returns a jwt.MapClaims, the real underlying type is the one you chose when calling Security.GenerateToken. Example:

token, err := fuego.TokenFromContext[MyCustomTokenType](ctx.Context())

func TokenFromCookie

func TokenFromCookie(r *http.Request) string

func TokenFromHeader

func TokenFromHeader(r *http.Request) string

func TokenFromQueryParam

func TokenFromQueryParam(r *http.Request) string

func Use

func Use(s *Server, middlewares ...func(http.Handler) http.Handler)

func UseStd

func UseStd(s *Server, middlewares ...func(http.Handler) http.Handler)

func WithAddr added in v0.13.0

func WithAddr(addr string) func(*Server)

WithAddr optionally specifies the TCP address for the server to listen on, in the form "host:port". If not specified addr ':9999' will be used.

func WithAutoAuth

func WithAutoAuth(verifyUserInfo func(user, password string) (jwt.Claims, error)) func(*Server)

func WithBasePath

func WithBasePath(basePath string) func(*Server)

func WithCorsMiddleware added in v0.13.0

func WithCorsMiddleware(corsMiddleware func(http.Handler) http.Handler) func(*Server)

WithCorsMiddleware registers a middleware to handle CORS. It is not handled like other middlewares with Use because it applies routes that are not registered. For example:

import "github.com/rs/cors"

s := fuego.NewServer(
	WithCorsMiddleware(cors.New(cors.Options{
		AllowedOrigins:   []string{"*"},
		AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowedHeaders:   []string{"*"},
		AllowCredentials: true,
	}))
)

func WithDisallowUnknownFields

func WithDisallowUnknownFields(b bool) func(*Server)

WithDisallowUnknownFields sets the DisallowUnknownFields option. If true, the server will return an error if the request body contains unknown fields. Useful for quick debugging in development. Defaults to true.

func WithErrorHandler

func WithErrorHandler(errorHandler func(err error) error) func(*Server)

func WithErrorSerializer

func WithErrorSerializer(serializer func(w http.ResponseWriter, err error)) func(*Server)

func WithLogHandler added in v0.10.0

func WithLogHandler(handler slog.Handler) func(*Server)

WithLogHandler sets the log handler of the server.

func WithMaxBodySize

func WithMaxBodySize(maxBodySize int64) func(*Server)

func WithOpenAPIConfig added in v0.13.0

func WithOpenAPIConfig(openapiConfig OpenAPIConfig) func(*Server)

func WithPort

func WithPort(port int) func(*Server)

WithPort sets the port of the server. For example, 8080. If not specified, the default port is 9999. If you want to use a different address, use WithAddr instead.

func WithSerializer

func WithSerializer(serializer func(w http.ResponseWriter, ans any)) func(*Server)

func WithTemplateFS

func WithTemplateFS(fs fs.FS) func(*Server)

WithTemplateFS sets the filesystem used to load templates. To be used with WithTemplateGlobs or WithTemplates. For example:

WithTemplateFS(os.DirFS("./templates"))

or with embedded templates:

//go:embed templates
var templates embed.FS
...
WithTemplateFS(templates)

func WithTemplateGlobs

func WithTemplateGlobs(patterns ...string) func(*Server)

WithTemplateGlobs loads templates matching the given patterns from the server filesystem. If the server filesystem is not set, it will use the os filesystem, at folder "./templates". For example:

WithTemplateGlobs("*.html, */*.html", "*/*/*.html")
WithTemplateGlobs("pages/*.html", "pages/admin/*.html")

for reference about the glob patterns in Go (no ** support for example): https://pkg.go.dev/path/filepath?utm_source=godoc#Match

func WithTemplates

func WithTemplates(templates *template.Template) func(*Server)

WithTemplates loads the templates used to render HTML. To be used with WithTemplateFS. If not set, it will use the os filesystem, at folder "./templates".

func WithValidator added in v0.13.0

func WithValidator(newValidator *validator.Validate) func(*Server)

WithValidator sets the validator to be used by the fuego server. If no validator is provided, a default validator will be used.

Note: If you are using the default validator, you can add tags to your structs using the `validate` tag. For example:

type MyStruct struct {
	Field1 string `validate:"required"`
	Field2 int    `validate:"min=10,max=20"`
}

The above struct will be validated using the default validator, and if any errors occur, they will be returned as part of the response.

func WithValue added in v0.10.0

func WithValue(ctx context.Context, val any) context.Context

func WithXML

func WithXML() func(*Server)

func WithoutLogger

func WithoutLogger() func(*Server)

WithoutLogger disables the default logger.

Types

type AutoAuthConfig

type AutoAuthConfig struct {
	Enabled        bool
	VerifyUserInfo func(user, password string) (jwt.Claims, error) // Must check the username and password, and return the claims
}

type BadRequestError

type BadRequestError HTTPError

BadRequestError is an error used to return a 400 status code.

func (BadRequestError) Error

func (e BadRequestError) Error() string

func (BadRequestError) StatusCode added in v0.13.0

func (e BadRequestError) StatusCode() int

func (BadRequestError) Unwrap added in v0.13.0

func (e BadRequestError) Unwrap() error

type ConflictError added in v0.13.0

type ConflictError HTTPError

ConflictError is an error used to return a 409 status code.

func (ConflictError) Error added in v0.13.0

func (e ConflictError) Error() string

func (ConflictError) StatusCode added in v0.13.0

func (e ConflictError) StatusCode() int

func (ConflictError) Unwrap added in v0.13.0

func (e ConflictError) Unwrap() error

type ContextNoBody added in v0.10.0

type ContextNoBody struct {
	Req *http.Request
	Res http.ResponseWriter
	// contains filtered or unexported fields
}

ContextNoBody is used when the controller does not have a body. It is used as a base context for other Context types.

func (ContextNoBody) Body added in v0.10.0

func (c ContextNoBody) Body() (any, error)

func (ContextNoBody) Context added in v0.10.0

func (c ContextNoBody) Context() context.Context

ContextNoBody implements the context interface via net/http.Request.Context

func (ContextNoBody) Cookie added in v0.13.0

func (c ContextNoBody) Cookie(name string) (*http.Cookie, error)

Get request cookie

func (ContextNoBody) Deadline added in v0.13.3

func (c ContextNoBody) Deadline() (deadline time.Time, ok bool)

ContextNoBody implements the context interface via net/http.Request.Context

func (ContextNoBody) Done added in v0.13.3

func (c ContextNoBody) Done() <-chan struct{}

ContextNoBody implements the context interface via net/http.Request.Context

func (ContextNoBody) Err added in v0.13.3

func (c ContextNoBody) Err() error

ContextNoBody implements the context interface via net/http.Request.Context

func (ContextNoBody) Header added in v0.13.0

func (c ContextNoBody) Header(key string) string

Get request header

func (ContextNoBody) MainLang added in v0.10.0

func (c ContextNoBody) MainLang() string

func (ContextNoBody) MainLocale added in v0.10.0

func (c ContextNoBody) MainLocale() string

func (ContextNoBody) MustBody added in v0.10.0

func (c ContextNoBody) MustBody() any

func (ContextNoBody) PathParam added in v0.10.0

func (c ContextNoBody) PathParam(name string) string

PathParams returns the path parameters of the request.

func (ContextNoBody) QueryParam added in v0.10.0

func (c ContextNoBody) QueryParam(name string) string

QueryParam returns the query parameter with the given name.

func (ContextNoBody) QueryParamBool added in v0.10.0

func (c ContextNoBody) QueryParamBool(name string, defaultValue bool) bool

func (ContextNoBody) QueryParamBoolErr added in v0.10.0

func (c ContextNoBody) QueryParamBoolErr(name string) (bool, error)

QueryParamBool returns the query parameter with the given name as a bool. If the query parameter does not exist or is not a bool, it returns nil. Accepted values are defined as strconv.ParseBool

func (ContextNoBody) QueryParamInt added in v0.10.0

func (c ContextNoBody) QueryParamInt(name string, defaultValue int) int

func (ContextNoBody) QueryParamIntErr added in v0.10.0

func (c ContextNoBody) QueryParamIntErr(name string) (int, error)

func (ContextNoBody) QueryParams added in v0.10.0

func (c ContextNoBody) QueryParams() map[string]string

QueryParams returns the query parameters of the request.

func (ContextNoBody) Redirect added in v0.10.0

func (c ContextNoBody) Redirect(code int, url string) (any, error)

func (ContextNoBody) Render added in v0.10.0

func (c ContextNoBody) Render(templateToExecute string, data any, layoutsGlobs ...string) (HTML, error)

Render renders the given templates with the given data. It returns just an empty string, because the response is written directly to the http.ResponseWriter.

Init templates if not already done. This have the side effect of making the Render method static, meaning that the templates will be parsed only once, removing the need to parse the templates on each request but also preventing to dynamically use new templates.

func (ContextNoBody) Request added in v0.10.0

func (c ContextNoBody) Request() *http.Request

Request returns the http request.

func (ContextNoBody) Response added in v0.10.0

func (c ContextNoBody) Response() http.ResponseWriter

Response returns the http response writer.

func (ContextNoBody) SetCookie added in v0.13.0

func (c ContextNoBody) SetCookie(cookie http.Cookie)

Sets response cookie

Example
s := NewServer()
Get(s, "/test", func(c *ContextNoBody) (string, error) {
	c.SetCookie(http.Cookie{
		Name:  "name",
		Value: "value",
	})
	return "test", nil
})

w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/test", nil)

s.Mux.ServeHTTP(w, r)

fmt.Println(w.Result().Cookies()[0].Name)
fmt.Println(w.Result().Cookies()[0].Value)
Output:

name
value

func (ContextNoBody) SetHeader added in v0.13.0

func (c ContextNoBody) SetHeader(key, value string)

Sets response header

Example
s := NewServer()
Get(s, "/test", func(c *ContextNoBody) (string, error) {
	c.SetHeader("X-Test", "test")
	return "test", nil
})

w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/test", nil)

s.Mux.ServeHTTP(w, r)

fmt.Println(w.Header().Get("X-Test"))
Output:

test

func (ContextNoBody) SetStatus added in v0.10.0

func (c ContextNoBody) SetStatus(code int)

SetStatus sets the status code of the response. Alias to http.ResponseWriter.WriteHeader.

func (ContextNoBody) Value added in v0.13.3

func (c ContextNoBody) Value(key any) any

ContextNoBody implements the context interface via net/http.Request.Context

type ContextWithBody added in v0.10.0

type ContextWithBody[Body any] struct {
	ContextNoBody
	// contains filtered or unexported fields
}

ContextWithBody is the same as fuego.ContextNoBody, but has a Body. The Body type parameter repesents the expected data type from http.Request.Body. Please do not use a pointer as a type parameter.

func NewContext

func NewContext[B any](w http.ResponseWriter, r *http.Request, options readOptions) *ContextWithBody[B]

NewContext returns a new context. It is used internally by Fuego. You probably want to use Ctx[B] instead.

func (*ContextWithBody[B]) Body added in v0.10.0

func (c *ContextWithBody[B]) Body() (B, error)

Body returns the body of the request. If (*B) implements InTransformer, it will be transformed after deserialization. It caches the result, so it can be called multiple times. The reason why the body is cached is because it is not possible to read an http request body multiple times, not because of performance. For decoding, it uses the Content-Type header. If it is not set, defaults to application/json.

func (*ContextWithBody[B]) MustBody added in v0.10.0

func (c *ContextWithBody[B]) MustBody() B

MustBody works like Body, but panics if there is an error.

type CtxRenderer added in v0.9.0

type CtxRenderer interface {
	Render(context.Context, io.Writer) error
}

CtxRenderer can be used with github.com/a-h/templ Example:

func getRecipes(ctx fuego.ContextNoBody) (fuego.CtxRenderer, error) {
	recipes, err := ctx.store.GetRecipes(ctx.Context())
	if err != nil {
		return nil, err
	}

	return recipeComponent(recipes), nil // recipeComponent is templ component
}

type ErrorItem added in v0.13.0

type ErrorItem struct {
	Name   string         `json:"name"`
	Reason string         `json:"reason"`
	More   map[string]any `json:"more,omitempty"`
}

type ErrorWithStatus

type ErrorWithStatus interface {
	error
	StatusCode() int
}

ErrorWithStatus is an interface that can be implemented by an error to provide additional information about the error.

type ForbiddenError added in v0.13.0

type ForbiddenError HTTPError

ForbiddenError is an error used to return a 403 status code.

func (ForbiddenError) Error added in v0.13.0

func (e ForbiddenError) Error() string

func (ForbiddenError) StatusCode added in v0.13.0

func (e ForbiddenError) StatusCode() int

func (ForbiddenError) Unwrap added in v0.13.0

func (e ForbiddenError) Unwrap() error

type Gomponent added in v0.9.0

type Gomponent = Renderer

Gomponent is a shortcut for Renderer, which can be used with github.com/maragudk/gomponents

type H

type H map[string]any

H is a shortcut for map[string]any

type HTML

type HTML string

HTML is a marker type used to differentiate between a string response and an HTML response. To use templating, use [Ctx.Render].

type HTTPError

type HTTPError struct {
	Err      error       `json:"-" xml:"-"`                               // Developer readable error message. Not shown to the user to avoid security leaks.
	Type     string      `json:"type,omitempty" xml:"type,omitempty"`     // URL of the error type. Can be used to lookup the error in a documentation
	Title    string      `json:"title,omitempty" xml:"title,omitempty"`   // Short title of the error
	Status   int         `json:"status,omitempty" xml:"status,omitempty"` // HTTP status code. If using a different type than [HTTPError], for example [BadRequestError], this will be automatically overridden after Fuego error handling.
	Detail   string      `json:"detail,omitempty" xml:"detail,omitempty"` // Human readable error message
	Instance string      `json:"instance,omitempty" xml:"instance,omitempty"`
	Errors   []ErrorItem `json:"errors,omitempty" xml:"errors,omitempty"`
}

HTTPError is the error response used by the serialization part of the framework.

func (HTTPError) Error

func (e HTTPError) Error() string

func (HTTPError) StatusCode

func (e HTTPError) StatusCode() int

func (HTTPError) Unwrap added in v0.13.0

func (e HTTPError) Unwrap() error

type InTransformer

type InTransformer interface {
	InTransform(context.Context) error // InTransforms the entity.
}

InTransformer is an interface for entities that can be transformed. Useful for example for trimming strings, changing case, etc. Can also raise an error if the entity is not valid.

type LoginPayload

type LoginPayload struct {
	User     string `json:"user" validate:"required"` // Might be an email, a username, or anything else that identifies uniquely the user
	Password string `json:"password" validate:"required"`
}

type NotFoundError added in v0.13.0

type NotFoundError HTTPError

NotFoundError is an error used to return a 404 status code.

func (NotFoundError) Error added in v0.13.0

func (e NotFoundError) Error() string

func (NotFoundError) StatusCode added in v0.13.0

func (e NotFoundError) StatusCode() int

func (NotFoundError) Unwrap added in v0.13.0

func (e NotFoundError) Unwrap() error

type OpenAPIConfig added in v0.13.0

type OpenAPIConfig struct {
	DisableSwagger   bool                              // If true, the server will not serve the swagger ui nor the openapi json spec
	DisableLocalSave bool                              // If true, the server will not save the openapi json spec locally
	SwaggerUrl       string                            // URL to serve the swagger ui
	UIHandler        func(specURL string) http.Handler // Handler to serve the openapi ui from spec url
	JsonUrl          string                            // URL to serve the openapi json spec
	JsonFilePath     string                            // Local path to save the openapi json spec
	PrettyFormatJson bool                              // Pretty prints the open api spec with proper json indentation
}

type OpenAPIParam added in v0.11.0

type OpenAPIParam struct {
	Required bool
	Example  string
	Type     string // "query", "header", "cookie"
}

type OutTransformer

type OutTransformer interface {
	OutTransform(context.Context) error // Transforms an entity before sending it.
}

OutTransformer is an interface for entities that can be transformed. Useful for example for trimming strings, changing case, etc. Can also raise an error if the entity is not valid. Must be implemented by a POINTER RECEIVER. Example:

type User struct {
	Name     string `json:"name"`
	Password string `json:"password"`
}

// Not (u User) but (u *User)

func (u *User) OutTransform(context.Context) error {
	u.Name = "M. " + u.Name
	u.Password = "*****"
	return nil
}

type QueryParamInvalidTypeError

type QueryParamInvalidTypeError struct {
	ParamName    string
	ParamValue   string
	ExpectedType string
	Err          error
}

func (QueryParamInvalidTypeError) Error

type QueryParamNotFoundError

type QueryParamNotFoundError struct {
	ParamName string
}

func (QueryParamNotFoundError) Error

func (e QueryParamNotFoundError) Error() string

type Renderer added in v0.9.0

type Renderer interface {
	Render(io.Writer) error
}

Renderer can be used with github.com/maragudk/gomponents Example:

func getRecipes(ctx fuego.ContextNoBody) (fuego.CtxRenderer, error) {
	recipes, err := ctx.store.GetRecipes(ctx.Context())
	if err != nil {
		return nil, err
	}

	return recipeComponent(recipes), nil // recipeComponent is gomponents component
}

type Route

type Route[ResponseBody any, RequestBody any] struct {
	// contains filtered or unexported fields
}

func All

func All[T any, B any, Contexted ctx[B]](s *Server, path string, controller func(Contexted) (T, error), middlewares ...func(http.Handler) http.Handler) Route[T, B]

Capture all methods (GET, POST, PUT, PATCH, DELETE) and register a controller.

func Delete

func Delete[T any, B any, Contexted ctx[B]](s *Server, path string, controller func(Contexted) (T, error), middlewares ...func(http.Handler) http.Handler) Route[T, B]

func DeleteStd

func DeleteStd(s *Server, path string, controller func(http.ResponseWriter, *http.Request), middlewares ...func(http.Handler) http.Handler) Route[any, any]

func Get

func Get[T any, B any, Contexted ctx[B]](s *Server, path string, controller func(Contexted) (T, error), middlewares ...func(http.Handler) http.Handler) Route[T, B]

func GetStd

func GetStd(s *Server, path string, controller func(http.ResponseWriter, *http.Request), middlewares ...func(http.Handler) http.Handler) Route[any, any]

func Handle

func Handle(s *Server, path string, controller http.Handler, middlewares ...func(http.Handler) http.Handler) Route[any, any]

Handle registers a standard http handler into the default mux. Use this function if you want to use a standard http handler instead of a fuego controller.

func Patch

func Patch[T any, B any, Contexted ctx[B]](s *Server, path string, controller func(Contexted) (T, error), middlewares ...func(http.Handler) http.Handler) Route[T, B]

func PatchStd

func PatchStd(s *Server, path string, controller func(http.ResponseWriter, *http.Request), middlewares ...func(http.Handler) http.Handler) Route[any, any]

func Post

func Post[T any, B any, Contexted ctx[B]](s *Server, path string, controller func(Contexted) (T, error), middlewares ...func(http.Handler) http.Handler) Route[T, B]

func PostStd

func PostStd(s *Server, path string, controller func(http.ResponseWriter, *http.Request), middlewares ...func(http.Handler) http.Handler) Route[any, any]

func Put

func Put[T any, B any, Contexted ctx[B]](s *Server, path string, controller func(Contexted) (T, error), middlewares ...func(http.Handler) http.Handler) Route[T, B]

func PutStd

func PutStd(s *Server, path string, controller func(http.ResponseWriter, *http.Request), middlewares ...func(http.Handler) http.Handler) Route[any, any]

func Register

func Register[T any, B any, Contexted ctx[B]](s *Server, method string, path string, controller func(Contexted) (T, error), middlewares ...func(http.Handler) http.Handler) Route[T, B]

Registers route into the default mux.

func RegisterStd

func RegisterStd(s *Server, method string, path string, controller func(http.ResponseWriter, *http.Request), middlewares ...func(http.Handler) http.Handler) Route[any, any]

RegisterStd registers a standard http handler into the default mux.

func (Route[ResponseBody, RequestBody]) AddTags

func (r Route[ResponseBody, RequestBody]) AddTags(tags ...string) Route[ResponseBody, RequestBody]

AddTags adds tags to the route.

func (Route[ResponseBody, RequestBody]) Cookie added in v0.11.0

func (r Route[ResponseBody, RequestBody]) Cookie(name, description string, params ...OpenAPIParam) Route[ResponseBody, RequestBody]

Cookie registers a cookie parameter for the route.

func (Route[ResponseBody, RequestBody]) Deprecated added in v0.11.0

func (r Route[ResponseBody, RequestBody]) Deprecated() Route[ResponseBody, RequestBody]

func (Route[ResponseBody, RequestBody]) Description added in v0.11.0

func (r Route[ResponseBody, RequestBody]) Description(description string) Route[ResponseBody, RequestBody]

func (Route[ResponseBody, RequestBody]) Header added in v0.11.0

func (r Route[ResponseBody, RequestBody]) Header(name, description string, params ...OpenAPIParam) Route[ResponseBody, RequestBody]

Header registers a header parameter for the route.

func (Route[ResponseBody, RequestBody]) OperationID added in v0.11.0

func (r Route[ResponseBody, RequestBody]) OperationID(operationID string) Route[ResponseBody, RequestBody]

Overrides the operationID for the route.

func (Route[ResponseBody, RequestBody]) Param added in v0.11.0

func (r Route[ResponseBody, RequestBody]) Param(paramType, name, description string, params ...OpenAPIParam) Route[ResponseBody, RequestBody]

Param registers a parameter for the route. The paramType can be "query", "header" or "cookie". [Cookie], [Header], [QueryParam] are shortcuts for Param.

func (Route[ResponseBody, RequestBody]) QueryParam added in v0.11.0

func (r Route[ResponseBody, RequestBody]) QueryParam(name, description string, params ...OpenAPIParam) Route[ResponseBody, RequestBody]

QueryParam registers a query parameter for the route.

func (Route[ResponseBody, RequestBody]) RemoveTags added in v0.10.0

func (r Route[ResponseBody, RequestBody]) RemoveTags(tags ...string) Route[ResponseBody, RequestBody]

RemoveTags removes tags from the route.

func (Route[ResponseBody, RequestBody]) Summary added in v0.11.0

func (r Route[ResponseBody, RequestBody]) Summary(summary string) Route[ResponseBody, RequestBody]

func (Route[ResponseBody, RequestBody]) Tags added in v0.11.0

func (r Route[ResponseBody, RequestBody]) Tags(tags ...string) Route[ResponseBody, RequestBody]

Replace the tags for the route. By default, the tag is the type of the response body.

type Security

type Security struct {
	Now             func() time.Time
	ExpiresInterval time.Duration
	// contains filtered or unexported fields
}

Security holds the key to sign the JWT tokens, and configuration information. The key isn't accessible once created to avoid leaking it. To use it, please use the methods provided.

func NewSecurity

func NewSecurity() Security

func (Security) CookieLogoutHandler

func (security Security) CookieLogoutHandler(w http.ResponseWriter, r *http.Request)

RemoveTokenFromCookies generates a JWT token with the given claims and writes it to the cookies. Usage:

fuego.PostStd(s, "/auth/logout", security.CookieLogoutHandler)

Dependancy to Security is for symmetry with [RefreshHandler].

func (Security) GenerateToken

func (security Security) GenerateToken(claims jwt.Claims) (token string, err error)

GenerateToken generates a JWT token with the given claims. The claims must be a jwt.MapClaims or embed jwt.RegisteredClaims.

func (Security) GenerateTokenToCookies

func (security Security) GenerateTokenToCookies(claims jwt.Claims, w http.ResponseWriter) (string, error)

GenerateTokenToCookies generates a JWT token with the given claims and writes it to the cookies.

func (Security) LoginHandler

func (security Security) LoginHandler(verifyUserInfo func(user, password string) (jwt.Claims, error)) func(*ContextWithBody[LoginPayload]) (tokenResponse, error)

LoginHandler is a premade login handler. It takes a function that checks if the user is authorized. Example:

security := fuego.NewSecurity()
security.ExpiresInterval = 24 * time.Hour
fuego.Post(s, "/login", security.LoginHandler(verifyUserInfo))
...
func verifyUserInfo(r *http.Request) (jwt.Claims, error) {
	// Get the username and password from the request
	username := r.FormValue("username")
	password := r.FormValue("password")
	// ...
	// Check if the username and password are correct.
	// Usually, you would check in a database.
	if username != "myUsername" || password != "myPassword" {
		return nil, errors.New("invalid username or password")
	}
	// ...
	// Return the claims
	return &MyCustomToken{
		It is recommanded to embed jwt.RegisteredClaims in your custom struct that will define your JWT.
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    username,
			Subject:   username,
			Audience:  jwt.ClaimStrings{"aud1", "aud2"},
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			ID:        "1234567890",
		},
		Username: "myUsername",
	}, nil

func (Security) RefreshHandler

func (security Security) RefreshHandler(w http.ResponseWriter, r *http.Request)

RefreshHandler is a premade refresh handler. It refreshes the token with the same information as the previous one, but with a new issued date. It sends the new token to the cookies and to the response. Usage:

fuego.PostStd(s, "/auth/refresh", security.RefreshHandler)

func (Security) StdLoginHandler

func (security Security) StdLoginHandler(verifyUserInfo func(r *http.Request) (jwt.Claims, error)) func(w http.ResponseWriter, r *http.Request)

StdLoginHandler is a premade login handler. It takes a function that checks if the user is authorized. Example:

security := fuego.NewSecurity()
security.ExpiresInterval = 24 * time.Hour
fuego.Post(s, "/login", security.StdLoginHandler(verifyUserInfo))
...
func verifyUserInfo(r *http.Request) (jwt.Claims, error) {
	// Get the username and password from the request
	username := r.FormValue("username")
	password := r.FormValue("password")
	// ...
	// Check if the username and password are correct.
	// Usually, you would check in a database.
	if username != "myUsername" || password != "myPassword" {
		return nil, errors.New("invalid username or password")
	}
	// ...
	// Return the claims
	return &MyCustomToken{
		It is recommanded to embed jwt.RegisteredClaims in your custom struct that will define your JWT.
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    username,
			Subject:   username,
			Audience:  jwt.ClaimStrings{"aud1", "aud2"},
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			ID:        "1234567890",
		},
		Username: "myUsername",
	}, nil

func (Security) TokenToContext

func (security Security) TokenToContext(searchFunc ...func(*http.Request) string) func(next http.Handler) http.Handler

TokenToContext is a middleware that checks if the user is authenticated from various authentication methods. Once found, the token is parsed, validated and the claims are set in the context. TLDR: after this middleware, the token is either non-existent or validated. You can use TokenFromContext to get the claims

func (Security) ValidateToken

func (security Security) ValidateToken(token string) (*jwt.Token, error)

type Server

type Server struct {
	// The underlying http server
	*http.Server

	// Will be plugged into the Server field.
	// Not using directly the Server field so
	// [http.ServeMux.Handle] can also be used to register routes.
	Mux *http.ServeMux

	OpenApiSpec openapi3.T // OpenAPI spec generated by the server

	Security Security

	DisallowUnknownFields bool // If true, the server will return an error if the request body contains unknown fields. Useful for quick debugging in development.
	DisableOpenapi        bool // If true, the the routes within the server will not generate an openapi spec.

	Serialize      func(w http.ResponseWriter, ans any)   // Used to serialize the response. Defaults to [SendJSON].
	SerializeError func(w http.ResponseWriter, err error) // Used to serialize the error response. Defaults to [SendJSONError].
	ErrorHandler   func(err error) error                  // Used to transform any error into a unified error type structure with status code. Defaults to [ErrorHandler]

	OpenAPIConfig OpenAPIConfig
	// contains filtered or unexported fields
}

func Group

func Group(s *Server, path string) *Server

Group allows to group routes under a common path. Middlewares are scoped to the group. For example:

s := fuego.NewServer()
viewsRoutes := fuego.Group(s, "")
apiRoutes := fuego.Group(s, "/api")
// Registering a middlewares scoped to /api only
fuego.Use(apiRoutes, myMiddleware)
// Registering a route under /api/users
fuego.Get(apiRoutes, "/users", func(c fuego.ContextNoBody) (ans, error) {
	return ans{Ans: "users"}, nil
})
s.Run()

func NewServer

func NewServer(options ...func(*Server)) *Server

NewServer creates a new server with the given options. For example:

app := fuego.NewServer(
	fuego.WithAddr(":8080"),
	fuego.WithoutLogger(),
)

Option all begin with `With`. Some default options are set in the function body.

func (*Server) AddTags added in v0.13.0

func (s *Server) AddTags(tags ...string) *Server

AddTags adds tags from the Server (i.e Group) Tags from the parent Groups will be respected

func (*Server) Hide added in v0.13.0

func (s *Server) Hide() *Server

Hide prevents the routes in this server or group from being included in the OpenAPI spec.

func (*Server) OutputOpenAPISpec added in v0.13.0

func (s *Server) OutputOpenAPISpec() openapi3.T

OutputOpenAPISpec takes the OpenAPI spec and outputs it to a JSON file and/or serves it on a URL. Also serves a Swagger UI. To modify its behavior, use the WithOpenAPIConfig option.

func (*Server) RemoveTags added in v0.13.0

func (s *Server) RemoveTags(tags ...string) *Server

RemoveTags removes tags from the Server (i.e Group) if the parent Group(s) has matching tags they will be removed

func (*Server) Run

func (s *Server) Run() error

Run starts the server. It is blocking. It returns an error if the server could not start (it could not bind to the port for example). It also generates the OpenAPI spec and outputs it to a file, the UI, and a handler (if enabled).

func (*Server) Show added in v0.13.0

func (s *Server) Show() *Server

Show allows to display the routes. Activated by default so useless in most cases, but this can be useful if you desactivated the parent group.

func (*Server) Tags added in v0.13.0

func (s *Server) Tags(tags ...string) *Server

Replaces Tags for the Server (i.e Group) By default, the tag is the type of the response body.

type Templ added in v0.9.0

type Templ = CtxRenderer

Templ is a shortcut for CtxRenderer, which can be used with github.com/a-h/templ

type Timing added in v0.10.0

type Timing struct {
	Name string
	Dur  time.Duration
	Desc string
}

Timing is a struct to represent a server timing. Used in the Server-Timing header.

func (Timing) String added in v0.10.0

func (t Timing) String() string

String returns a string representation of a Timing, as defined in https://www.w3.org/TR/server-timing/#the-server-timing-header-field

type UnauthorizedError added in v0.13.0

type UnauthorizedError HTTPError

UnauthorizedError is an error used to return a 401 status code.

func (UnauthorizedError) Error added in v0.13.0

func (e UnauthorizedError) Error() string

func (UnauthorizedError) StatusCode added in v0.13.0

func (e UnauthorizedError) StatusCode() int

func (UnauthorizedError) Unwrap added in v0.13.0

func (e UnauthorizedError) Unwrap() error

Directories

Path Synopsis
cmd
fuego Module
examples
basic Module
hello-world Module
openapi Module
middleware

Jump to

Keyboard shortcuts

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