goal

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2023 License: MIT Imports: 13 Imported by: 0

README

Goal

A type-safe REST API framework for Go!

Inspired by FastAPI, types are used to define and validate path parameters, request bodies and responses.

Automatically generates OpenAPI 3 schema documentation and serves it using Swagger.

It builds upon chi (github.com/go-chi/chi) for routing and rest (github.com/a-h/rest) for generating the OpenAPI spec.

var randomComic = models.Comic{
	Title:  "Spider-Man",
	Author: "Stan Lee",
}

var randomUser = models.User{
	ID:             1,
	Name:           "Phillip J Fry",
	FavouriteComic: randomComic,
}

func AddRoutes(router *goal.Router) {
	// A goal route.
	goal.Get("/", router, func(c goal.CtxR[any]) (int, any) {
		return http.StatusOK, nil
	})

	// A goal route which returns a string to be sent in the http response.
	goal.Get("/ping", router, func(c goal.CtxR[any]) (int, string) {
		return http.StatusOK, "pong"
	})

	// An int.
	goal.Get("/answer", router, func(c goal.CtxR[any]) (int, int) {
		finished := true
		if !finished {
			/*
				An unsuccessful status (not >= 200 and < 300)
				means the placeholder 0 is not sent in the response.
			*/
			return http.StatusNotFound, 0
		}

		return http.StatusOK, 42
	})

	// Complex return types from goal routes are serialized to JSON.
	goal.Get("/user/random", router, func(c goal.CtxR[any]) (int, models.User) {
		return http.StatusOK, randomUser
	})

	/*
		These routes have no path parameters, so no type argument
		is passed to the Read Context (goal.CtxR[any]).
	*/
	goal.Get("/users", router, func(c goal.CtxR[any]) (int, []models.User) {
		return http.StatusOK, []models.User{randomUser, randomUser, randomUser}
	})

	/*
		Path parameter types are automatically validated (ID integer from url string).
		The path parameters type is passed to the CtxR (goal.CtxR[IDPath]).
	*/
	type IDPath struct {
		ID int
	}
	goal.Get("/user/{ID}", router, func(c goal.CtxR[IDPath]) (int, models.User) {
		found := c.Path.ID == randomUser.ID
		if !found {
			// Unsuccessful status means no JSON is sent.
			return http.StatusNotFound, models.User{}
		}
		return http.StatusOK, randomUser
	})

	// Order doesn't matter, only the name (must be capitalized for Go to export the field).
	type NameAgePath struct {
		Name string
		Age  int
	}
	goal.Get("/user/{Age}/{Name}", router, func(c goal.CtxR[NameAgePath]) (int, models.User) {
		forbidden := c.Path.Age < 18
		if forbidden {
			return http.StatusForbidden, models.User{}
		}
		found := c.Path.Name == randomUser.Name
		if !found {
			return http.StatusNotFound, models.User{}
		}
		return http.StatusOK, randomUser
	})

	/*
		No path parameters and no request body means no types are passed
		to the Create/Update/Delete Context (goal.CtxCUD[any, any]).
	*/
	goal.Post("/user/ping", router, func(c goal.CtxCUD[any, any]) (int, models.User) {
		return http.StatusOK, randomUser
	})

	// No request body type is passed to the CtxCUD, but the path type is (goal.CtxCUD[NamePath, any]).
	type NamePath struct {
		Name string
	}
	goal.Post("/user/{Name}", router, func(c goal.CtxCUD[NamePath, any]) (int, models.User) {
		newUser := models.User{Name: c.Path.Name}
		// Successful status code means the content is sent in the response
		return http.StatusCreated, newUser
	})

	// No path type is passed, but the request body type is (goal.CtxCUD[any, models.User]).
	goal.Put("/user", router, func(c goal.CtxCUD[any, models.User]) (int, models.User) {
		updatedUser := c.Body
		return http.StatusOK, updatedUser
	})

	// Both a path type and a request body type are passed (goal.CtxCUD[TitlePath, models.User]).
	type TitlePath struct {
		Title string
	}
	goal.Post("/user/{Title}", router, func(c goal.CtxCUD[TitlePath, models.User]) (int, models.User) {
		newUser := c.Body
		if c.Path.Title == randomComic.Title {
			newUser.FavouriteComic = randomComic
		}
		return http.StatusOK, newUser
	})

	// Handler function and types defined in outer scope.
	goal.Delete("/user/incomplete/{Age}", router, HandlePostIncompleteUser)
}

type AgePath struct {
	Age int
}

// Ad-hoc request body type.
type IncompleteUser struct {
	FavouriteComic models.Comic
}

func HandlePostIncompleteUser(c goal.CtxCUD[AgePath, IncompleteUser]) (int, models.User) {
	forbidden := c.Path.Age < 18
	if forbidden {
		return http.StatusForbidden, models.User{}
	}

	found := randomUser.FavouriteComic == c.Body.FavouriteComic
	if !found {
		return http.StatusNotFound, models.User{}
	}

	userToDelete := randomUser
	fmt.Println("Deleting", userToDelete)

	// 204 No Content status means no JSON will be sent.
	return http.StatusNoContent, models.User{}
}

/*
	Path parameters, request bodies and return types
	are documented in the automatically generated OpenAPI schema.
*/

/*
	Standard chi routes can be defined using the goal router.
	router.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("pong"))
	})
	If you do, don't nest with this router within one with goal routes because you won't
	be able to create an OpenAPI schema without additional type information.
*/

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Delete

func Delete[PathType, BodyType, ResponseType any](pattern string, router *Router, handlerFnCUD HandlerFnCUD[PathType, BodyType, ResponseType])

func Get

func Get[PathType, ResponseType any](pattern string, router *Router, handlerFnR HandlerFnR[PathType, ResponseType])

func Post

func Post[PathType, BodyType, ResponseType any](pattern string, router *Router, handlerFnCUD HandlerFnCUD[PathType, BodyType, ResponseType])

func Put

func Put[PathType, BodyType, ResponseType any](pattern string, router *Router, handlerFnCUD HandlerFnCUD[PathType, BodyType, ResponseType])

Types

type CtxCUD

type CtxCUD[PathType, BodyType any] struct {
	Writer  HeaderWriter
	Request *http.Request
	Path    PathType
	Body    BodyType
}

type CtxR

type CtxR[PathType any] struct {
	Writer  HeaderWriter
	Request *http.Request
	Path    PathType
}

type HandlerFnCUD

type HandlerFnCUD[PathType, BodyType, ResponseType any] func(CtxCUD[PathType, BodyType]) (int, ResponseType)

type HandlerFnR

type HandlerFnR[PathType, ResponseType any] func(CtxR[PathType]) (int, ResponseType)

type HeaderWriter

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

func (*HeaderWriter) Header

func (headerWriter *HeaderWriter) Header() http.Header

type Router

type Router struct {
	chi.Mux
	Api *rest.API
}

func NewRouter

func NewRouter(name string) *Router

func (*Router) CreateSpec

func (r *Router) CreateSpec(version string, description string) *openapi3.T

func (*Router) ServeSwaggerUI

func (r *Router) ServeSwaggerUI(spec *openapi3.T, path string)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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