japi

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: May 27, 2022 License: MIT Imports: 14 Imported by: 0

README

japi is a JSON HTTP API go library

Japi is a fast & simple HTTP API library that will automatically marshal JSON payloads to/from your request and response structs. It follows RFC7807 standard for returning useful problem details.

This library focuses on happy path to minimize code and dependencies. For more complex use cases, we recommend sticking to a larger web framework. However, this library supports the standard net/http ecosystem.

This library requires Go 1.18 to work as it utilizes generics.

This library was forked from https://github.com/AbeMedia/go-don

Contents

Basic Example

package main

import (
  "context"
  "errors"
  "fmt"
  "net/http"

  "github.com/jarrettv/go-japi"
)

type GreetRequest struct {
  Name string `path:"name"`         // Get name from the URL path.
  Age  int    `header:"X-User-Age"` // Get age from HTTP header.
}

type GreetResponse struct {
  // Remember to add tags for automatic marshalling
  Greeting string `json:"data"`
}

func Greet(ctx context.Context, req GreetRequest) (*GreetResponse, error) {
  if req.Name == "" {
    return nil, japi.ProblemValid(map[string]string{
      "name": "required",
    })
  }
  res := &GreetResponse{
    Greeting: fmt.Sprintf("Hello %s, you're %d years old.", req.Name, req.Age),
  }

  return res, nil
}

func Pong(context.Context, japi.Empty) (string, error) {
  return "pong", nil
}

func main() {
  r := japi.New(nil)
  r.Get("/ping", japi.H(Pong)) // Handlers are wrapped with `japi.H`.
  r.Post("/greet/:name", japi.H(Greet))
  r.ListenAndServe(":8080")
}

Configuration

Japi is configured by passing in the Config struct to japi.New. We recommend you setup problem config at a minimum.

r := japi.New(&japi.Config{
  ProblemConfig: problem.ProblemConfig{
    ProblemTypeUrlFormat: "https://example.com/errors/%s",
    ProblemInstanceFunc: func(ctx context.Context) string {
      return fmt.Sprintf("https://example.com/trace/%d", time.Now().UnixMilli())
    },
  },
})
RouteLogFunc

A function to easily log the route name and route variables.

ProblemLogFunc

A function to easily log when problems occur.

ProblemConfig.ProblemTypeUrlFormat

The format for the problem details type URI. See RFC7807

ProblemConfig.ProblemInstanceFunc

A function for generating a unique trace URI. Defaults to a timestamp. See RFC7807

Request parsing

Automatically unmarshals values from headers, URL query, URL path & request body into your request struct.

type MyRequest struct {
  // Get from the URL path.
  ID int64 `path:"id"`

  // Get from the URL query.
  Filter string `query:"filter"`

  // Get from the JSON, YAML, XML or form body.
  Content float64 `form:"bar" json:"bar"`

  // Get from the HTTP header.
  Lang string `header:"Accept-Language"`
}

Please note that using a pointer as the request type negatively affects performance.

Customize Response

Implement the StatusCoder and Headerer interfaces to customise headers and response codes.

type MyResponse struct {
  Foo  string `json:"foo"`
}

// Set a custom HTTP response code.
func (nr *MyResponse) StatusCode() int {
  return 201
}

// Add custom headers to the response.
func (nr *MyResponse) Header() http.Header {
  header := http.Header{}
  header.Set("foo", "bar")
  return header
}

Problems

Return a problem.Problem error when something goes wrong. For example:

return nil, problem.Unexpected(err) // 500
// or
return nil, problem.NotFound() // 404
// or
return nil, problem.NotPermitted(username) // 403
// or
return nil, problem.Validation(map[string]string{ // 400
  "name": "required",
})
// or
return nil, problem.RuleViolantion("item is on backorder") // 400
// or
return nil, problem.NotCurrent() // 407

Sub-routers

You can create sub-routers using the Group function:

r := japi.New(nil)
sub := r.Group("/api")
sub.Get("/hello")

Middleware

Japi uses the standard http middleware format of func(http.RequestHandler) http.RequestHandler.

For example:

func loggingMiddleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)  {
    log.Println(r.URL)
    next(ctx)
  })
}

It is registered on a router using Use e.g.

r := japi.New(nil)
r.Post("/", japi.H(handler))
r.Use(loggingMiddleware)

Middleware registered on a group only applies to routes in that group and child groups.

r := japi.New(nil)
r.Get("/login", japi.H(loginHandler))
r.Use(loggingMiddleware) // applied to all routes

api := r.Group("/api")
api.Get("/hello", japi.H(helloHandler))
api.Use(authMiddleware) // applied to routes `/api/hello` and `/api/v2/bye`


v2 := api.Group("/v2")
v2.Get("/bye", japi.H(byeHandler))
v2.Use(corsMiddleware) // only applied to `/api/v2/bye`

To pass values from the middleware to the handler extend the context e.g.

func myMiddleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)  {
    ctx := context.WithValue(r.Context(), ContextUserKey, "my_user")
    next.ServeHTTP(w, r.WithContext(ctx))
  })
}

This can now be accessed in the handler:

user := ctx.Value(ContextUserKey).(string)

Documentation

Index

Constants

View Source
const JsonEncoding = "application/json"

Variables

This section is empty.

Functions

func LoadRawJson

func LoadRawJson(loadRawJson func() ([]byte, error)) http.HandlerFunc

LoadRawJson loads raw json to response useful for swagger docs.

func RawJson

func RawJson(data []byte) http.HandlerFunc

RawJson sends raw json to response useful for swagger docs.

func RegisterDecoder

func RegisterDecoder[T DecoderConstraint](contentType string, dec T, aliases ...string)

RegisterDecoder registers a request decoder.

Types

type API

type API struct {
	NotFound         http.Handler
	MethodNotAllowed http.Handler
	PanicHandler     func(http.ResponseWriter, *http.Request, interface{})
	// contains filtered or unexported fields
}

func New

func New(c *Config) *API

New creates a new API instance.

func (*API) Delete

func (r *API) Delete(path string, handle http.Handler)

Delete handles DELETE requests.

func (*API) Get

func (r *API) Get(path string, handle http.Handler)

Get handles GET requests.

func (*API) Group

func (r *API) Group(path string) Router

Group creates a new sub-router with the given prefix.

func (*API) Handle

func (r *API) Handle(method, path string, handle http.Handler)

Handle can be used to wrap regular handlers.

func (*API) HandleFunc

func (r *API) HandleFunc(method, path string, handle http.HandlerFunc)

HandleFunc handles the requests with the specified method.

func (*API) Patch

func (r *API) Patch(path string, handle http.Handler)

Patch handles PATCH requests.

func (*API) Post

func (r *API) Post(path string, handle http.Handler)

Post handles POST requests.

func (*API) Put

func (r *API) Put(path string, handle http.Handler)

Put handles PUT requests.

func (*API) Router

func (r *API) Router() http.Handler

Router creates a http.Handler for the API.

func (*API) Use

func (r *API) Use(mw ...Middleware)

Use will register middleware to run prior to the handlers.

type Config

type Config struct {
	// the function to call for logging route details
	RouteLogFunc func(ctx context.Context, route string, params map[string]string)
	// the function to call for logging problems
	ProblemLogFunc func(ctx context.Context, p *problem.Problem)
	problem.ProblemConfig
}

func GetDefaultConfig

func GetDefaultConfig() *Config

GetDefaultConfig will return default problem config.

type ContextUnmarshaler

type ContextUnmarshaler = func(ctx context.Context, data []byte, v interface{}) error

type DecoderConstraint

type DecoderConstraint interface {
	Unmarshaler | ContextUnmarshaler | DecoderFactory | RequestParser
}

type DecoderFactory

type DecoderFactory = func(io.Reader) interface{ Decode(interface{}) error }

type Empty

type Empty struct{}

type Handle

type Handle[T any, O any] func(ctx context.Context, request T) (O, error)

Handle is the type for your handlers.

type Handler

type Handler interface {
	http.Handler
	// contains filtered or unexported methods
}

Handler allows you to handle request with the route params

func E

func E(err error) Handler

E creates a Handler that returns the error

func H

func H[T any, O any](handle Handle[T, O]) Handler

H wraps your handler function with the Go generics magic.

type Headerer

type Headerer interface {
	Header() http.Header
}

Headerer allows you to customise the HTTP headers.

type Middleware

type Middleware func(http.Handler) http.Handler

type Problemer

type Problemer interface {
	Problem() problem.Problem
}

Problemer allows you to customize the error problem details.

type RequestParser

type RequestParser = func(r *http.Request, v interface{}) error

type Router

type Router interface {
	Get(path string, handle http.Handler)
	Post(path string, handle http.Handler)
	Put(path string, handle http.Handler)
	Delete(path string, handle http.Handler)
	Handle(method, path string, handle http.Handler)
	HandleFunc(method, path string, handle http.HandlerFunc)
	Group(path string) Router
	Use(mw ...Middleware)
}

type StatusCoder

type StatusCoder interface {
	StatusCode() int
}

StatusCoder allows you to customise the HTTP response code.

type Unmarshaler

type Unmarshaler = func(data []byte, v interface{}) error

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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