simba

package module
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Dec 10, 2024 License: MIT Imports: 20 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.HandlerFunc(handler))
	app.Start(context.Background())
}

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.HandlerFunc(getUser))

Logging

Simba automatically injects an slog logger into the request's context. To access the logger, use the logging.From function in Simba's logging package:

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": "01JE61MX24YEGF08E8G0RA0Y14",
  "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.AuthHandlerFunc(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

View Source
const (
	RequestIDKey    requestIdContextKey = "requestId"
	RequestIDHeader string              = "X-Request-Id"
)

Variables

This section is empty.

Functions

func AuthHandlerFunc added in v0.3.0

func AuthHandlerFunc[RequestBody any, Params any, AuthModel any](h AuthenticatedHandler[RequestBody, Params, AuthModel]) http.Handler

AuthHandlerFunc returns an http.Handler that can be used for authenticated routes

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 HandlerFunc

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

HandlerFunc returns an http.Handler that can be used for non-authenticated routes

Types

type Application

type Application[AuthModel any] struct {

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

	// Router is the main router for the application
	Router *Router
	// contains filtered or unexported fields
}

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

func Default

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

Default returns a new Application application with default Settings

func DefaultAuthWith added in v0.3.0

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

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

func New

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

New returns a new Application application

func NewAuthWith added in v0.3.0

func NewAuthWith[User any](authFunc AuthFunc[User], provided ...Settings) *Application[User]

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]) GetLogger added in v0.3.0

func (a *Application[AuthModel]) GetLogger() *slog.Logger

GetLogger returns the default Simba application logger

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

func (a *Application[AuthModel]) Start(ctx context.Context)

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 AuthenticatedHandler

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

AuthenticatedHandler 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], user *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:

router.POST("/test/:id", simba.AuthenticatedHandlerFunc(handler))

func (AuthenticatedHandler[RequestBody, Params, AuthModel]) ServeHTTP

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

ServeHTTP implements the http.Handler interface for AuthenticatedHandler

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
	Message        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 WrapErrorHTTP

func WrapErrorHTTP(httpStatusCode int, err error, message string) *HTTPError

WrapErrorHTTP 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 Handler

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

Handler 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:

router.POST("/test/:id", simba.HandlerFunc(handler))

func (Handler[RequestBody, Params]) ServeHTTP

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

ServeHTTP implements the http.Handler interface for Handler

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 RequestSettings added in v0.3.0

type RequestSettings struct {

	// AllowUnknownFields will set the behavior for unknown fields in the Request body,
	// resulting in a 400 Bad Request response if a field is present that cannot be
	// mapped to the model struct.
	AllowUnknownFields enums.AllowOrNot `default:"Disallow"`

	// LogRequestBody will determine if the Request body will be logged
	// If set to "disabled", the Request body will not be logged, which is also the default
	LogRequestBody enums.EnableDisable `default:"Disabled"`

	// RequestIdMode determines how the Request ID will be handled
	RequestIdMode enums.RequestIdMode `default:"AcceptFromHeader"`
}

RequestSettings holds the Settings for the Request processing

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 {
	// contains filtered or unexported fields
}

Router is a simple router that wraps [httprouter.Router] 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 alice.Chain)

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) 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(next http.Handler) http.Handler)

Use registers a middleware handler

type ServerSettings added in v0.4.0

type ServerSettings struct {

	// Host is the host the server will listen on
	Host string `default:"0.0.0.0"`

	// Addr is the address the server will listen on
	Port int `default:"9999"`
}

ServerSettings holds the Settings for the application server

type Settings added in v0.2.0

type Settings struct {

	// Server settings
	Server ServerSettings

	// Request settings
	Request RequestSettings

	// Logging settings
	Logging logging.Config
}

Settings is a struct that holds the application Settings

type ValidationError

type ValidationError struct {
	// Parameter 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 (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