simba

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Dec 4, 2024 License: MIT Imports: 18 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
  • High performance through httprouter

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.POST("/users", simba.HandlerFunc(handler))
    http.ListenAndServe(":9999", app.GetRouter())
}

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.DefaultWithAuth[User](authFunc)
app.GET("/users/:userId", simba.AuthenticatedHandlerFunc(getUser))

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 a zerolog logger into every request's context. You can access this logger from any handler or middleware using the logging package:

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

Configuration

Customize behavior with options:

app := simba.New(simba.Options{
    RequestDisallowUnknownFields: true,
    RequestIdAcceptHeader:        true,
    LogRequestBody:               true,
    LogLevel:                     zerolog.DebugLevel,
    LogFormat:                    logging.JsonFormat,
    LogOutput:                    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": "2023-01-01T12:00:00Z",
  "status": 400,
  "error": "Bad Request",
  "path": "/api/resource",
  "method": "POST",
  "requestId": "01JE61MX24YEGF08E8G0RA0Y14",
  "message": "invalid request body",
  "validationErrors": [
    {
      "parameter": "email",
      "type": "body",
      "message": "'email' is not a valid email address"
    }
  ]
}

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 AuthenticatedHandlerFunc

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

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

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

func Default

func Default() *Application[struct{}]

Default returns a new Application application with default settings

func DefaultWithAuth

func DefaultWithAuth[AuthModel any](authFunc AuthFunc[AuthModel]) *Application[AuthModel]

DefaultWithAuth 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(opts ...Options) *Application[struct{}]

New returns a new Application application

func NewWithAuth

func NewWithAuth[User any](authFunc AuthFunc[User], opts ...Options) *Application[User]

NewWithAuth 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]) CONNECT

func (s *Application[AuthModel]) CONNECT(path string, handler http.Handler)

CONNECT registers a handler for CONNECT requests to the given pattern

func (*Application[AuthModel]) DELETE

func (s *Application[AuthModel]) DELETE(path string, handler http.Handler)

DELETE registers a handler for DELETE requests to the given pattern

func (*Application[AuthModel]) GET

func (s *Application[AuthModel]) GET(path string, handler http.Handler)

GET registers a handler for GET requests to the given pattern

func (*Application[AuthModel]) GetOptions

func (s *Application[AuthModel]) GetOptions() Options

GetOptions returns the options for the application

func (*Application[AuthModel]) GetRouter

func (s *Application[AuthModel]) GetRouter() *httprouter.Router

GetRouter returns the underlying httprouter.Router

func (*Application[AuthModel]) HEAD

func (s *Application[AuthModel]) HEAD(path string, handler http.Handler)

HEAD registers a handler for HEAD requests to the given pattern

func (*Application[AuthModel]) OPTIONS

func (s *Application[AuthModel]) OPTIONS(path string, handler http.Handler)

OPTIONS registers a handler for OPTIONS requests to the given pattern

func (*Application[AuthModel]) PATCH

func (s *Application[AuthModel]) PATCH(path string, handler http.Handler)

PATCH registers a handler for PATCH requests to the given pattern

func (*Application[AuthModel]) POST

func (s *Application[AuthModel]) POST(path string, handler http.Handler)

POST registers a handler for POST requests to the given pattern

func (*Application[AuthModel]) PUT

func (s *Application[AuthModel]) PUT(path string, handler http.Handler)

PUT registers a handler for PUT requests to the given pattern

func (*Application[AuthModel]) TRACE

func (s *Application[AuthModel]) TRACE(path string, handler http.Handler)

TRACE registers a handler for TRACE requests to the given pattern

func (*Application[AuthModel]) Use

func (s *Application[AuthModel]) Use(middleware func(next http.Handler) http.Handler)

Use registers a middleware handler

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 Options

type Options struct {
	// RequestDisallowUnknownFields will disallow 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.
	RequestDisallowUnknownFields bool

	// RequestIdHeader will determine if the request ID should be read from the
	// request header. If not set, the request ID will be generated.
	RequestIdAcceptHeader bool

	// LogRequestBody will determine if the request body will be logged
	LogRequestBody bool

	// LogLevel is the log level for the logger that will be used
	LogLevel zerolog.Level

	// LogFormat is the log format for the logger that will be used
	LogFormat logging.LogFormat

	// LogOutput is the output for the logger that will be used
	// If not set, the output will be [os.Stdout]
	LogOutput io.Writer
}

type ParameterType

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

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