simba

package module
v0.10.6 Latest Latest
Warning

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

Go to latest
Published: Dec 18, 2024 License: MIT Imports: 23 Imported by: 0

README

Simba

Simba is a lightweight, type-safe HTTP router framework for Go that makes building REST APIs simple and enjoyable. It provides strong type safety through generics and a clean, intuitive API for handling HTTP requests.

Features

  • Type-safe routing with Go generics
  • Built-in authentication support
  • Middleware support
  • Strong request/response typing

Installation

go get github.com/sillen102/simba

Quick Start

Here's a simple example showing how to create a basic HTTP server with Simba:

package main

import (
    "context"
    "fmt"
    "net/http"
    "github.com/sillen102/simba"
)

type RequestBody struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

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

func handler(ctx context.Context, req *simba.Request[RequestBody, simba.NoParams]) (*simba.Response, error) {

    // Access the request body fields
    // req.Body.Age
    // req.Body.Name

    // Access the request cookies
    // req.Cookies

    // Access the request headers
    // req.Headers

    return &simba.Response{
        Headers: map[string][]string{"My-Header": {"header-value"}},
        Cookies: []*http.Cookie{{Name: "My-Cookie", Value: "cookie-value"}},
        Body: ResponseBody{
            Message: fmt.Sprintf("Hello %s, you are %d years old", req.Body.Name, req.Body.Age),
        },
        Status: http.StatusOK, // Can be omitted, defaults to 200 if there's a body, 204 if there's no body
    }, nil
}

func main() {
    // Using simba.Default() will use the default options for logging and request validation,
    // add default middleware like panic recovery and request id and add some endpoints like /health
    //
    // If you wish to build up your own router without any default middleware etc., use simba.New()
    app := simba.Default()
	app.Router.POST("/users", simba.JsonHandler(handler))
	app.Start()
}

Parameters

Handle parameters with type safety and validation support using go-playground validator:

type Params struct {
    UserID string `path:"userId"`
    Name   string `query:"name" validate:"required"`
    Age    int    `header:"age" validate:"required"`
    Page   int64  `query:"page" validate:"omitempty,min=0" default:"0"`
    Size   int64  `query:"size" validate:"omitempty,min=0" default:"10"`
}

func getUser(ctx context.Context, req *simba.Request[simba.NoBody, Params]) (*simba.Response, error) {
    userID := req.Params.UserID
    name := req.Params.Name
    age := req.Params.Age

    // ... handle the request
}

app.GET("/users/{userId}", simba.JsonHandler(getUser))

Logging

Simba relies on slog to handle logging. If no logger is provided slog.Default will be used. If you use the Default or DefaultWithAuth constructors an slog logger will be injected into the request context for all requests. To access the injected logger, use the logging.From function the logging package.

Example:

func handler(ctx context.Context, req *simba.Request[simba.NoBody, simba.NoParams]) (*simba.Response, error) {
    logger := logging.From(ctx)
    logger.Info("handling request")
    // ... handle the request
}

Configuration

Customize behavior with settings:

app := simba.New(simba.Settings{
    Server: simba.ServerSettings{
        Host: "localhost",
        Port: 9999,
    },
    Request: simba.RequestSettings{
        AllowUnknownFields: enums.Allow,
        LogRequestBody: enums.Enabled,
        RequestIdMode: enums.AcceptFromHeader,
    },
    Logging: logging.Config{
        Level: slog.LevelInfo,
        Format: logging.JsonFormat,
        Output: os.Stdout,
    },
})

Error Handling

Simba provides automatic error handling with standardized JSON responses. All errors are automatically wrapped and returned in a consistent format:

{
  "timestamp": "2024-12-04T20:28:33.852965Z",
  "status": 400,
  "error": "Bad Request",
  "path": "/api/resource",
  "method": "POST",
  "requestId": "40ad8bb4-215a-4748-8a7f-9e236d988c5b",
  "message": "request validation failed, 1 validation error",
  "validationErrors": [
    {
      "parameter": "email",
      "type": "body",
      "message": "'notanemail' is not a valid email address"
    }
  ]
}

Middleware

Simba supports middleware. Simply create a function that takes a handler and returns a handler and register it with the Use method on the router:

func myMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        r.Header.Set("X-Middleware", "123") // Here we simply add a header to every request
        next.ServeHTTP(w, r) // And the proceed to the next handler
    })
}

app.Router.Use(myMiddleware)

Authentication

Simba provides built-in support for authentication:

type User struct {
    ID   string
    Name string
}

func authFunc(r *http.Request) (*User, error) {
    // Your authentication logic here, either be it a database lookup or any other authentication method
    return &User{ID: "123", Name: "John"}, nil
}

// This handler will only be called if the user is authenticated and the user is available as one of the function parameters
func getUser(ctx context.Context, req *simba.Request[simba.NoBody, simba.NoParams], user *User) (*simba.Response, error) {
    // ... handle the request
}

app := simba.DefaultAuthWith(authFunc)
app.GET("/users/{userId}", simba.AuthJsonHandler(getUser))

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

All dependencies are under their respective licenses, which can be found in their repositories via the go.mod file.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AuthJsonHandler added in v0.6.0

func AuthJsonHandler[RequestBody any, Params any, AuthModel any](h AuthenticatedJsonHandlerFunc[RequestBody, Params, AuthModel]) http.Handler

AuthJsonHandler handles a Request with the Request body and params.

Example usage:

Define a Request body struct:

type RequestBody struct {
	Test string `json:"test" validate:"required"`
}

Define a Request params struct:

type Params struct {
	Name   string `header:"name" validate:"required"`
	ID     int    `path:"id" validate:"required"`
	Active bool   `query:"active" validate:"required"`
	Page   int64  `query:"page" validate:"min=0"`
	Size   int64  `query:"size" validate:"min=0"`
}

Define a user struct:

type AuthModel struct {
	ID   int
	Name string
	Role string
}

Define a handler function:

func(ctx context.Context, req *simba.Request[RequestBody, Params], authModel *AuthModel) (*simba.Response, error) {
	// Access the Request body and params fields
	req.Body.Test
	req.Params.Name
	req.Params.ID
	req.Params.Page
	req.Params.Size

	// Access the user fields
	user.ID
	user.Name
	user.Role

	// Return a response
	return &simba.Response{
		Headers: map[string][]string{"My-Header": {"header-value"}},
		Cookies: []*http.Cookie{{Name: "My-Cookie", Value: "cookie-value"}},
		Body:    map[string]string{"message": "success"},
		Status:  http.StatusOK,
	}, nil
}

Register the handler:

Mux.POST("/test/{id}", simba.AuthJsonHandler(handler))

func AuthMultipartHandler added in v0.7.0

func AuthMultipartHandler[Params any, AuthModel any](h AuthenticatedMultipartHandlerFunc[Params, AuthModel]) http.Handler

AuthMultipartHandler handles a MultipartRequest with params and an authenticated model. The MultipartRequest holds a MultipartReader and the parsed params. The reason to provide the reader is to allow the logic for processing the parts to be handled by the handler function.

Example usage:

Define a Request params struct:

type Params struct {
	Name   string `header:"name" validate:"required"`
	ID     int    `path:"id" validate:"required"`
	Active bool   `query:"active" validate:"required"`
	Page   int64  `query:"page" validate:"min=0"`
	Size   int64  `query:"size" validate:"min=0"`
}

Define a user struct:

type AuthModel struct {
	ID   int
	Name string
	Role string
}

Define a handler function:

func(ctx context.Context, req *simba.MultipartRequest[Params], authModel *AuthModel) (*simba.Response, error) {
	// Access the Multipart reader and params fields
	req.Params.Name
	req.Params.ID
	req.Params.Page
	req.Params.Size
	req.Reader // Multipart reader

	// Access the user fields
	user.ID
	user.Name
	user.Role

	// Return a response
	return &simba.Response{
		Headers: map[string][]string{"My-Header": {"header-value"}},
		Cookies: []*http.Cookie{{Name: "My-Cookie", Value: "cookie-value"}},
		Body:    map[string]string{"message": "success"},
		Status:  http.StatusOK,
	}, nil
}

Register the handler:

Mux.POST("/test/{id}", simba.AuthMultipartHandler(handler))

func HandleError added in v0.1.1

func HandleError(w http.ResponseWriter, r *http.Request, err error)

HandleError is a helper function for handling errors in HTTP handlers

func JsonHandler added in v0.6.0

func JsonHandler[RequestBody any, Params any](h JsonHandlerFunc[RequestBody, Params]) http.Handler

JsonHandler handles a Request with the Request body and params.

Example usage:

Define a Request body struct:

type RequestBody struct {
	Test string `json:"test" validate:"required"`
}

Define a Request params struct:

type Params struct {
	Name   string `header:"name" validate:"required"`
	ID     int    `path:"id" validate:"required"`
	Active bool   `query:"active" validate:"required"`
	Page   int64  `query:"page" validate:"min=0"`
	Size   int64  `query:"size" validate:"min=0"`
}

Define a handler function:

func(ctx context.Context, req *simba.Request[RequestBody, Params]) (*simba.Response, error) {
	// Access the Request body and params fields
	req.Body.Test
	req.Params.Name
	req.Params.ID
	req.Params.Page
	req.Params.Size

	// Return a response
	return &simba.Response{
		Headers: map[string][]string{"My-Header": {"header-value"}},
		Cookies: []*http.Cookie{{Name: "My-Cookie", Value: "cookie-value"}},
		Body:    map[string]string{"message": "success"},
		Status:  http.StatusOK,
	}, nil
}

Register the handler:

Mux.POST("/test/{id}", simba.JsonHandler(handler))

func MapValidationMessage added in v0.10.6

func MapValidationMessage(e validator.FieldError, value string) string

MapValidationMessage returns appropriate error message based on the validation tag

func MultipartHandler added in v0.7.0

func MultipartHandler[Params any](h MultipartHandlerFunc[Params]) http.Handler

MultipartHandler handles a MultipartRequest with params. // The MultipartRequest holds a MultipartReader and the parsed params. // The reason to provide the reader is to allow the logic for processing the parts to be handled by the handler function.

Example usage:

Define a Request params struct:

type Params struct {
	Name   string `header:"name" validate:"required"`
	ID     int    `path:"id" validate:"required"`
	Active bool   `query:"active" validate:"required"`
	Page   int64  `query:"page" validate:"min=0"`
	Size   int64  `query:"size" validate:"min=0"`
}

Define a handler function:

func(ctx context.Context, req *simba.Request[RequestBody, Params]) (*simba.Response, error) {
	// Access the Multipart reader and params fields
	req.Params.Name
	req.Params.ID
	req.Params.Page
	req.Params.Size
	req.Reader // Multipart reader

	// Return a response
	return &simba.Response{
		Headers: map[string][]string{"My-Header": {"header-value"}},
		Cookies: []*http.Cookie{{Name: "My-Cookie", Value: "cookie-value"}},
		Body:    map[string]string{"message": "success"},
		Status:  http.StatusOK,
	}, nil
}

Register the handler:

Mux.POST("/test/{id}", simba.MultipartHandler(handler))

Types

type Application

type Application[AuthModel any] struct {

	// Server is the HTTP server for the application
	Server *http.Server

	// Router is the main Mux for the application
	Router *Router

	// Settings is the application Settings
	Settings *settings.Config

	// AuthFunc is the function used to authenticate and retrieve the authenticated model
	// from the Request
	AuthFunc AuthFunc[AuthModel]
}

Application is the main application struct that holds the Mux and other application Settings

func Default

func Default(settings ...settings.Config) *Application[struct{}]

Default returns a new Application application with default Config

func DefaultAuthWith added in v0.3.0

func DefaultAuthWith[AuthModel any](authFunc AuthFunc[AuthModel], settings ...settings.Config) *Application[AuthModel]

DefaultAuthWith returns a new Application application with default Config and ability to have authenticated routes using the provided AuthFunc to authenticate and retrieve the user

func New

func New(settings ...settings.Config) *Application[struct{}]

New returns a new Application application

func NewAuthWith added in v0.3.0

func NewAuthWith[AuthModel any](authFunc AuthFunc[AuthModel], provided ...settings.Config) *Application[AuthModel]

NewAuthWith returns a new Application application with ability to have authenticated routes using the provided AuthFunc to authenticate and retrieve the authenticated model

func (*Application[AuthModel]) Start added in v0.4.0

func (a *Application[AuthModel]) Start()

func (*Application[AuthModel]) Stop added in v0.4.0

func (a *Application[AuthModel]) Stop() error

type AuthFunc

type AuthFunc[AuthModel any] func(r *http.Request) (*AuthModel, error)

AuthFunc is a function type for authenticating and retrieving an authenticated model struct from a Request

type AuthenticatedJsonHandlerFunc added in v0.6.0

type AuthenticatedJsonHandlerFunc[RequestBody any, Params any, AuthModel any] func(ctx context.Context, req *Request[RequestBody, Params], authModel *AuthModel) (*Response, error)

AuthenticatedJsonHandlerFunc is a function type for handling authenticated routes with Request body and params

func (AuthenticatedJsonHandlerFunc[RequestBody, Params, AuthModel]) ServeHTTP added in v0.6.0

func (h AuthenticatedJsonHandlerFunc[RequestBody, Params, AuthModel]) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements the http.Handler interface for AuthenticatedJsonHandlerFunc

type AuthenticatedMultipartHandlerFunc added in v0.7.0

type AuthenticatedMultipartHandlerFunc[Params any, AuthModel any] func(ctx context.Context, req *MultipartRequest[Params], authModel *AuthModel) (*Response, error)

AuthenticatedMultipartHandlerFunc is a function type for handling a MultipartRequest with params and an authenticated model

func (AuthenticatedMultipartHandlerFunc[Params, AuthModel]) ServeHTTP added in v0.7.0

func (h AuthenticatedMultipartHandlerFunc[Params, AuthModel]) ServeHTTP(w http.ResponseWriter, r *http.Request)

type ErrorResponse

type ErrorResponse struct {
	// Timestamp of the error
	Timestamp time.Time `json:"timestamp"`
	// HTTP status code
	Status int `json:"status"`
	// HTTP error type
	Error string `json:"error"`
	// Path of the Request
	Path string `json:"path"`
	// Method of the Request
	Method string `json:"method"`
	// Request ID
	RequestID string `json:"requestId,omitempty"`
	// Error message
	Message string `json:"message,omitempty"`
	// Validation errors
	ValidationErrors []ValidationError `json:"validationErrors,omitempty"`

} // @Name ErrorResponse

ErrorResponse defines the structure of an error message @Description Represents the structure of an error message returned by the API

func NewErrorResponse

func NewErrorResponse(r *http.Request, status int, message string, validationErrors ...ValidationError) *ErrorResponse

NewErrorResponse creates a new ErrorResponse instance with the given status and message

type HTTPError

type HTTPError struct {
	HttpStatusCode   int
	PublicMessage    string
	ValidationErrors ValidationErrors
	// contains filtered or unexported fields
}

func NewHttpError

func NewHttpError(httpStatusCode int, publicMessage string, err error, validationErrors ...ValidationError) *HTTPError

NewHttpError creates a new ApiError

func WrapError added in v0.8.1

func WrapError(httpStatusCode int, err error, publicMessage string, validationErrors ...ValidationError) *HTTPError

WrapError wraps an error with an HTTP status code

func (*HTTPError) Error

func (e *HTTPError) Error() string

Error implements the error interface and returns the full error details

func (*HTTPError) HasValidationErrors

func (e *HTTPError) HasValidationErrors() bool

HasValidationErrors checks if there are validation errors

func (*HTTPError) Unwrap

func (e *HTTPError) Unwrap() error

Unwrap returns the underlying error

type JsonHandlerFunc added in v0.6.0

type JsonHandlerFunc[RequestBody any, Params any] func(ctx context.Context, req *Request[RequestBody, Params]) (*Response, error)

JsonHandlerFunc is a function type for handling routes with Request body and params

func (JsonHandlerFunc[RequestBody, Params]) ServeHTTP added in v0.6.0

func (h JsonHandlerFunc[RequestBody, Params]) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements the http.Handler interface for JsonHandlerFunc

type MultipartHandlerFunc added in v0.7.0

type MultipartHandlerFunc[Params any] func(ctx context.Context, req *MultipartRequest[Params]) (*Response, error)

MultipartHandlerFunc is a function type for handling routes with Request body and params

func (MultipartHandlerFunc[Params]) ServeHTTP added in v0.7.0

func (h MultipartHandlerFunc[Params]) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements the http.Handler interface for JsonHandlerFunc

type MultipartRequest added in v0.7.0

type MultipartRequest[RequestParams any] struct {
	Cookies []*http.Cookie
	Reader  *multipart.Reader
	Params  RequestParams
}

type NoBody

type NoBody struct {
}

NoBody is an empty struct used to represent no body

type NoParams

type NoParams struct {
}

NoParams is an empty struct used to represent no params

type ParameterType

type ParameterType string
const (
	ParameterTypeHeader ParameterType = "header"
	ParameterTypePath   ParameterType = "path"
	ParameterTypeQuery  ParameterType = "query"
	ParameterTypeBody   ParameterType = "body"
)

func (ParameterType) String added in v0.3.0

func (p ParameterType) String() string

type Request

type Request[RequestBody any, RequestParams any] struct {
	Cookies []*http.Cookie
	Body    RequestBody
	Params  RequestParams
}

Request represents a HTTP Request

type Response

type Response struct {
	Headers http.Header
	Cookies []*http.Cookie
	Body    any
	Status  int
}

Response represents a HTTP response

type Router added in v0.2.0

type Router struct {
	Mux *http.ServeMux
	// contains filtered or unexported fields
}

Router is a simple Mux that wraps http.ServeMux and allows for middleware chaining

func (*Router) DELETE added in v0.2.0

func (r *Router) DELETE(path string, handler http.Handler)

DELETE registers a handler for DELETE requests to the given pattern

func (*Router) Extend added in v0.2.0

func (r *Router) Extend(middleware []func(http.Handler) http.Handler)

Extend extends the middleware chain with another chain

func (*Router) GET added in v0.2.0

func (r *Router) GET(path string, handler http.Handler)

GET registers a handler for GET requests to the given pattern

func (*Router) HEAD added in v0.2.0

func (r *Router) HEAD(path string, handler http.Handler)

HEAD registers a handler for HEAD requests to the given pattern

func (*Router) Handle added in v0.6.0

func (r *Router) Handle(pattern string, handler http.HandlerFunc)

Handle registers a standard lib handler for the given pattern

func (*Router) OPTIONS added in v0.2.0

func (r *Router) OPTIONS(path string, handler http.Handler)

OPTIONS registers a handler for OPTIONS requests to the given pattern

func (*Router) PATCH added in v0.2.0

func (r *Router) PATCH(path string, handler http.Handler)

PATCH registers a handler for PATCH requests to the given pattern

func (*Router) POST added in v0.2.0

func (r *Router) POST(path string, handler http.Handler)

POST registers a handler for POST requests to the given pattern

func (*Router) PUT added in v0.2.0

func (r *Router) PUT(path string, handler http.Handler)

PUT registers a handler for PUT requests to the given pattern

func (*Router) ServeHTTP added in v0.2.0

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP implements the http.Handler interface for the Router type

func (*Router) Use added in v0.2.0

func (r *Router) Use(middleware func(http.Handler) http.Handler)

Use registers a middleware handler

type ValidationError

type ValidationError struct {
	// Parameter or field that failed validation
	Parameter string `json:"parameter"`
	// Type indicates where the parameter was located (header, path, query, body)
	Type ParameterType `json:"type"`
	// Error message describing the validation error
	Message string `json:"message"`

} // @Name ValidationError

ValidationError defines the interface for a validation error @Description Detailed information about a validation error

type ValidationErrors

type ValidationErrors []ValidationError

ValidationErrors represents multiple validation errors

func ValidateStruct added in v0.10.4

func ValidateStruct(request any, paramType ParameterType) ValidationErrors

ValidateStruct is a helper function for validating requests using the validator package. If the request is nil, it will return nil. If the request is valid, it will return an empty slice of ValidationErrors. If the request is invalid, it will return a slice of ValidationErrors containing the validation errors for each field.

func (ValidationErrors) Error

func (ve ValidationErrors) Error() string

Error implements the error interface

Directories

Path Synopsis
websocket module

Jump to

Keyboard shortcuts

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