rip

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jul 26, 2023 License: BSD-3-Clause Imports: 10 Imported by: 1

README

RIP ⚰ Go Reference Go Report Card

REST in peace

Why?

Creating RESTful API in Go is in a way simple and fun in the first time, but also repetitive and error prone the more resource you handle.
Copy pasting nearly the same code for each resource you want to GET or POST to except for the request and response types is not that cool, and interface{} neither.
Let's get the best of both worlds with GENERICS 🎆 everybody screams 😱

How?

The idea would be to use the classic net/http package with handlers created from Go types.

http.HandleFunc(rip.HandleResource[User]("/users", NewUserProvider())

// in Go 1.21, you can skip the type specification
http.HandleFunc(rip.HandleResource("/users", NewUserProvider())

given that UserProvider implements the rip.ResourceProvider interface

// simplified version
type ResourceProvider[Rsc IdentifiableResource] interface {
	Create(ctx context.Context, res Rsc) (Rsc, error)
	Get(ctx context.Context, id IdentifiableResource) (Rsc, error)
	Update(ctx context.Context, res Rsc) error
	Delete(ctx context.Context, id IdentifiableResource) error
	ListAll(ctx context.Context) ([]Rsc, error)
}

and your resource implements the IdentifiableResource interface

type IdentifiableResource interface {
	IDString() string
	IDFromString(s string) error
}

Right now, it can talk several encoding in reading and writing: JSON, XML, YAML, msgpack, HTML and HTML form. Based on Accept and Content-Type headers, you can be asymmetrical in encoding: send JSON and read XML.

HTML/HTML Forms allows you to edit your resources directly from your web browser. It's very basic for now.

⚠️: Disclaimer, the API is not stable yet, use or contribute at your own risks

* Final code may differ from actual shown footage

Play with it

go run github.com/dolanor/rip/examples/srv-example@latest
// open your browser to http://localhost:8888 and start editing users

Features

  • support for multiple encoding automatically selected with Accept and Content-Type headers, or resource extension /resources/1.json
    • JSON
    • YAML
    • XML
    • msgpack
    • HTML (read version)
    • HTML forms (write version)
  • middlewares
  • automatic generation of HTML forms for live editing of resources

TODO

  • middleware support
  • I'd like to have more composability in the resource provider (some are read-only, some can't list, some are write only…), haven't figured out the right way to design that, yet.
  • it should work for nested resources
  • improve the error API
  • support for hypermedia discoverability
  • support for multiple data representation

Documentation

Overview

Example
package main

import (
	"context"
	"net/http"
	"time"
)

func main() {
	up := newUserProvider()
	http.HandleFunc(HandleResource("/users/", up))

	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		panic(err)
	}
}

type user struct {
	Name      string    `json:"name" xml:"name"`
	BirthDate time.Time `json:"birth_date" xml:"birth_date"`
}

func (u user) IDString() string {
	return u.Name
}

func (u *user) IDFromString(s string) error {
	u.Name = s

	return nil
}

type UserProvider struct {
	mem map[string]user
}

func newUserProvider() *UserProvider {
	return &UserProvider{
		mem: map[string]user{},
	}
}

func (up *UserProvider) Create(ctx context.Context, u *user) (*user, error) {
	up.mem[u.Name] = *u
	return u, nil
}

func (up UserProvider) Get(ctx context.Context, ider IdentifiableResource) (*user, error) {
	u, ok := up.mem[ider.IDString()]
	if !ok {
		return &user{}, Error{Code: ErrorCodeNotFound, Message: "user not found"}
	}
	return &u, nil
}

func (up *UserProvider) Delete(ctx context.Context, ider IdentifiableResource) error {
	_, ok := up.mem[ider.IDString()]
	if !ok {
		return Error{Code: ErrorCodeNotFound, Message: "user not found"}
	}

	delete(up.mem, ider.IDString())
	return nil
}

func (up *UserProvider) Update(ctx context.Context, u *user) error {
	_, ok := up.mem[u.Name]
	if !ok {
		return Error{Code: ErrorCodeNotFound, Message: "user not found"}
	}
	up.mem[u.Name] = *u

	return nil
}

func (up *UserProvider) ListAll(ctx context.Context) ([]*user, error) {
	var users []*user
	for _, u := range up.mem {
		// we copy to avoid referring the same pointer that would get updated
		u := u
		users = append(users, &u)
	}

	return users, nil
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Handle

func Handle[Request, Response any](method string, f RequestResponseFunc[Request, Response]) http.HandlerFunc

Handle is a generic HTTP handler that maps an HTTP method to a RequestResponseFunc f.

func HandleResource

func HandleResource[Rsc IdentifiableResource, RP ResourceProvider[Rsc]](urlPath string, rp RP, mids ...func(http.HandlerFunc) http.HandlerFunc) (path string, handler http.HandlerFunc)

HandleResource associates an urlPath with a resource provider, and handles all HTTP requests in a RESTful way:

POST   /resources/    : creates the resource
GET    /resources/:id : get the resource
PUT    /resources/:id : updates the resource (needs to pass the full resource data)
DELETE /resources/:id : deletes the resource
GET    /resources/    : lists the resources

Types

type Error

type Error struct {
	Status  int
	Code    ErrorCode
	Message string
}

Error is the error returned by rip.

func (Error) Error

func (e Error) Error() string

type ErrorCode

type ErrorCode int

ErrorCode maps errors from the ResourceProvider implementation to HTTP status code.

const (
	// ErrorCodeNotFound happens when a resource with an id is not found.
	ErrorCodeNotFound ErrorCode = http.StatusNotFound

	// ErrorCodeBadQArg happens when a user gives a wrongly formatted header `; q=X.Y` argument.
	ErrorCodeBadQArg ErrorCode = 499
)

type IdentifiableResource

type IdentifiableResource interface {
	// IDString returns an ID in form of a string.
	IDString() string

	// IDFromString serialize an ID from s.
	IDFromString(s string) error
}

IdentifiableResource is a resource that can be identified by an string.

type RequestResponseFunc

type RequestResponseFunc[Request, Response any] func(ctx context.Context, request Request) (response Response, err error)

RequestResponseFunc is a function that takes a ctx and a request, and it can return a response or an err.

type ResourceCreater added in v0.1.1

type ResourceCreater[Rsc IdentifiableResource] interface {
	Create(ctx context.Context, res Rsc) (Rsc, error)
}

ResourceCreater creates a resource that can be identified.

type ResourceDeleter added in v0.1.1

type ResourceDeleter[Rsc IdentifiableResource] interface {
	Delete(ctx context.Context, id IdentifiableResource) error
}

ResourceDeleter deletes a resource with its id.

type ResourceGetter added in v0.1.1

type ResourceGetter[Rsc IdentifiableResource] interface {
	Get(ctx context.Context, id IdentifiableResource) (Rsc, error)
}

ResourceGetter gets a resource with its id.

type ResourceLister added in v0.1.1

type ResourceLister[Rsc any] interface {
	ListAll(ctx context.Context) ([]Rsc, error)
}

ResourceLister lists a group of resources.

type ResourceProvider

type ResourceProvider[Rsc IdentifiableResource] interface {
	ResourceCreater[Rsc]
	ResourceGetter[Rsc]
	ResourceUpdater[Rsc]
	ResourceDeleter[Rsc]
	ResourceLister[Rsc]
}

ResourceProvider provides identifiable resources.

type ResourceUpdater added in v0.1.1

type ResourceUpdater[Rsc IdentifiableResource] interface {
	Update(ctx context.Context, res Rsc) error
}

ResourceUpdater updates an identifiable resource.

Directories

Path Synopsis
xml
examples
srv-example command

Jump to

Keyboard shortcuts

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