zeal

package module
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Mar 13, 2024 License: MIT Imports: 11 Imported by: 0

README

Zeal

A type-safe REST API framework for Go!

Structs can be used to define and validate URL parameters, request bodies and response types.

Automatically generates fully typed OpenAPI 3 schema documentation using REST and serves it with SwaggerUI.

Usage

func main() {
    mux := zeal.NewServeMux("Example API")
    addRoutes(mux)

    apiOptions := zeal.ApiOptions{
        Version:       "v0.1.0",
        Description:   "Example API description.",
        StripPkgPaths: []string{"main", "models", "github.com/DandyCodes/zeal"},
    }
    spec, err := mux.CreateAPI(apiOptions)
    if err != nil {
        log.Fatalf("Failed to create API: %v", err)
    }

    serveOptions := zeal.ServeOptions{
        Port:           3975,
        Spec:           spec,
        ServeSwaggerUI: true,
        SwaggerPattern: "/swagger-ui/",
    }
    mux.ListenAndServe(serveOptions)
}

Routes handled by Zeal are automatically documented in the OpenAPI schema

This route has no response type, no URL params and no request body

zeal.Handle(mux, "POST /", func(r zeal.Response[any], params any, body any) {
    fmt.Println("Hello, world!")
})

The response type is passed as a type parameter

This route responds with an int - zeal.Response[int]

zeal.Handle(mux, "GET /answer", func(r zeal.Response[int], p any, b any) {
    r.JSON(42)
})

The JSON method will only accept data of the declared response type


Example data

var foodMenu = models.Menu{
    ID:    1,
    Items: []models.Item{{Name: "Steak", Price: 13.95}, {Name: "Potatoes", Price: 3.95}},
}
var drinksMenu = models.Menu{
    ID:    2,
    Items: []models.Item{{Name: "Juice", Price: 1.25}, {Name: "Soda", Price: 1.75}},
}
var menus = []models.Menu{foodMenu, drinksMenu}

This route responds with a slice of menus - zeal.Response[[]models.Menu]

zeal.Handle(mux, "GET /menus", func(r zeal.Response[[]models.Menu], p any, b any) {
    r.JSON(menus, http.StatusOK)
})

The JSON method can be passed an optional HTTP status code (the route responds with 200 OK by default)


Params can be query or path params

Struct representing URL params can be defined in-line

zeal.Handle(mux, "GET /menus/{ID}", func(r zeal.Response[models.Menu], p struct{ ID int }, b any) {
    for _, menu := range menus {
        if menu.ID == p.ID {
            r.JSON(menu)
            return
        }
    }
    r.Error(http.StatusNotFound)
})

Params struct fields must be capitalized (i.e. 'Quiet')

type PutItemParams struct {
    Quiet bool
}
zeal.Handle(mux, "PUT /items",
    func(r zeal.Response[models.Item], p PutItemParams, item models.Item) {
        if item.Price < 0 {
            r.Error(http.StatusBadRequest, "Price cannot be negative")
            return
        }

        for i := range menus {
            for j := range menus[i].Items {
                if menus[i].Items[j].Name == item.Name {
                    if !p.Quiet {
                        fmt.Println("Updating item:", item)
                    }
                    menus[i].Items[j].Price = item.Price
                    updatedItem := menus[i].Items[j]
                    r.JSON(updatedItem)
                    return
                }
            }
        }

        if !p.Quiet {
            fmt.Println("Creating new item:", item)
        }
        menus[1].Items = append(menus[1].Items, item)
        updatedItem := menus[1].Items[len(menus[1].Items)-1]
        r.JSON(updatedItem, http.StatusCreated)
    })

Params and request bodies are converted to their declared type

If this fails, http.StatusUnprocessableEntity 422 is sent immediately

zeal.Handle(mux, "POST /items", HandlePostItem)

func HandlePostItem(r zeal.Response[any], p struct{ MenuID int }, newItem models.Item) {
    if newItem.Price < 0 {
        r.Error(http.StatusBadRequest, "Price cannot be negative")
        return
    }

    for i := range menus {
        if menus[i].ID != p.MenuID {
            continue
        }

        menus[i].Items = append(menus[i].Items, newItem)
        r.Status(http.StatusCreated)
        return
    }

    r.Error(http.StatusNotFound)
}

The Error method takes an HTTP status code and an optional error message

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Handle added in v0.5.0

func Handle[T_Response, T_Params, T_Body any](mux *ServeMux, pattern string, handlerFunc HandlerFunc[T_Response, T_Params, T_Body])

Types

type ApiOptions added in v0.5.1

type ApiOptions struct {
	Version       string
	Description   string
	StripPkgPaths []string
}

type HandlerFunc added in v0.3.0

type HandlerFunc[T_Response, T_Params, T_Body any] func(Response[T_Response], T_Params, T_Body)

type Response added in v0.5.1

type Response[T_Response any] struct {
	ResponseWriter http.ResponseWriter
	Request        *http.Request
}

func (Response[T_Response]) Error added in v0.5.1

func (r Response[T_Response]) Error(status int, errorMsg ...string)

func (*Response[T_Response]) JSON added in v0.5.1

func (r *Response[T_Response]) JSON(data T_Response, status ...int)

func (Response[T_Response]) Status added in v0.5.1

func (r Response[T_Response]) Status(status int)

type ServeMux added in v0.5.1

type ServeMux struct {
	*http.ServeMux
	Api *rest.API
}

func NewServeMux added in v0.5.1

func NewServeMux(apiName ...string) *ServeMux

func (*ServeMux) CreateAPI added in v0.5.1

func (mux *ServeMux) CreateAPI(options ApiOptions) (*openapi3.T, error)

func (*ServeMux) ListenAndServe added in v0.5.1

func (mux *ServeMux) ListenAndServe(options ServeOptions) error

func (*ServeMux) ServeSwaggerUI added in v0.5.1

func (mux *ServeMux) ServeSwaggerUI(spec *openapi3.T, path string) error

type ServeOptions added in v0.5.1

type ServeOptions struct {
	Port           int
	Spec           *openapi3.T
	ServeSwaggerUI bool
	SwaggerPattern string
}

type URLPathParamPlaceholder added in v0.5.1

type URLPathParamPlaceholder struct {
	// contains filtered or unexported fields
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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