babyapi

package module
v0.14.0 Latest Latest
Warning

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

Go to latest
Published: May 5, 2024 License: Apache-2.0 Imports: 25 Imported by: 7

README

Baby API

GitHub go.mod Go version (subdirectory of monorepo) GitHub Workflow Status License Go Reference codecov

A Go CRUD API framework so simple a baby could use it.

babyapi is a super simple framework that automatically creates an HTTP API for create, read, update, and delete operations on a struct. Simply extend the babyapi.DefaultResource type to get started.

Implement custom request/response handling by implemented Renderer and Binder from go-chi/render. Use provided extension functions to add additional API functionality:

  • OnCreateOrUpdate: additional handling for create/update requests
  • Storage: set a different storage backend implementing the babyapi.Storage interface
  • AddCustomRoute: add more routes on the base API
  • Patch: add custom logic for handling PATCH requests
  • And many more! (see examples and docs)
  • Override any of the default handlers and use babyapi.Handler shortcut to easily render errors and responses

Getting Started

  1. Create a new Go module:

    mkdir babyapi-example
    cd babyapi-example
    go mod init babyapi-example
    
  2. Write main.go to create a TODO struct and initialize babyapi.API:

    package main
    
    import "github.com/calvinmclean/babyapi"
    
    type TODO struct {
        babyapi.DefaultResource
    
        Title       string
        Description string
        Completed   bool
    }
    
    func main() {
        api := babyapi.NewAPI(
            "TODOs", "/todos",
            func() *TODO { return &TODO{} },
        )
        api.RunCLI()
    }
    
  3. Run!

    go mod tidy
    go run main.go serve
    
  4. Use the built-in CLI to interact with the API:

    # Create a new TODO
    go run main.go client todos post --data '{"title": "use babyapi!"}'
    
    # Get all TODOs
    go run main.go client todos list
    
    # Get TODO by ID (use ID from previous responses)
    go run main.go client todos get cljvfslo4020kglbctog
    
Simple Example

Client

In addition to providing the HTTP API backend, babyapi is also able to create a client that provides access to the base endpoints:

// Create a client from an existing API struct (mostly useful for unit testing):
client := api.Client(serverURL)

// Create a client from the Resource type:
client := babyapi.NewClient[*TODO](addr, "/todos")
// Create a new TODO item
todo, err := client.Post(context.Background(), &TODO{Title: "use babyapi!"})

// Get an existing TODO item by ID
todo, err := client.Get(context.Background(), todo.GetID())

// Get all incomplete TODO items
incompleteTODOs, err := client.GetAll(context.Background(), url.Values{
    "completed": []string{"false"},
})

// Delete a TODO item
err := client.Delete(context.Background(), todo.GetID())

The client provides methods for interacting with the base API and MakeRequest and MakeRequestWithResponse to interact with custom routes. You can replace the underlying http.Client and set a request editor function that can be used to set authorization headers for a client.

Testing

The babytest package provides some shortcuts and utilities for easily building table tests or simple individual tests. This allows seamlessly creating tests for an API using the convenient babytest.RequestTest struct, a function returning an *http.Request, or a slice of command-line arguments.

Check out some of the examples for examples of using the babytest package.

Storage

You can bring any storage backend to babyapi by implementing the Storage interface. By default, the API will use the built-in KVStorage with the default configuration for in-memory map.

This storage implementation leverages madflojo/hord to support a variety of key-value store backends. Currently, the babyapi/storage/kv package provides helpers to create file or redis-based storage implementations.

db, err := kv.NewFileDB(hashmap.Config{
    Filename: "storage.json",
})
db, err := kv.NewRedisDB(redis.Config{
    Server: "localhost:6379",
})

api.SetStorage(babyapi.NewKVStorage[*TODO](db, "TODO"))
EndDateable

The babyapi.EndDateable interface can be implemented to enable soft-delete with the KVStorage. This will set an end-date instead of permanently deleting a resource. Then, deleting it again will permanently delete. Also, the GetAll implementation will filter out end-dated resources unless the end_dated query parameter is set to enable getting end-dated resources.

Extensions

babyapi provides an Extension interface that can be applied to any API with api.ApplyExtension(). Implementations of this interface create custom configurations and modifications that can be applied to multiple APIs. A few extensions are provided by the babyapi/extensions package:

  • HATEOAS: "Hypertext as the engine of application state" is the 3rd and final level of REST API maturity, making your API fully RESTful
  • KVStorage: provide a few simple configurations to use the KVStorage client with a local file or Redis
  • HTMX: HTMX expects 200 responses from DELETE requests, so this changes the response code

Examples

Description Features
TODO list This example expands upon the base example to create a realistic TODO list application
  • Custom PATCH logic
  • Additional request validation
  • Automatically set CreatedAt field
  • Query parameter parsing to only show completed items
Nested resources Demonstrates how to build APIs with nested/related resources. The root resource is an Artist which can have Albums and MusicVideos. Then, Albums can have Songs
  • Nested API resources
  • Custom ResponseWrapper to add fields from related resources
  • HATEOAS Extension for hypermedia linking
Storage The example shows how to use the babyapi/storage package to implement persistent storage
  • Use SetStorage to use a custom storage implementation
  • Create a hord storage client using babyapi/storage
TODO list with HTMX UI This is a more complex example that demonstrates an application with HTMX frontend. It uses server-sent events to automatically update with newly-created items
  • Implement babyapi.HTMLer for HTML responses
  • Set custom HTTP response codes per HTTP method
  • Use built-in helpers for handling server-sent events on a custom route
  • Use SetOnCreateOrUpdate to do additional actions on create
  • Handle HTML forms as input instead of JSON (which works automatically and required no changes)
Event RSVP This is a more complex nested example that implements basic authentication, middlewares, and relationships between nested types. The app can be used to create Events and provide guests with a link to view details and RSVP
  • Demonstrates middlewares and nested resource relationships
  • Authentication
  • Custom non-CRUD endpoints
  • More complex HTML templating
Multiple APIs This example shows how multiple top-level (or any level) sibling APIs can be served, and have CLI functionality, under one root API
  • Use NewRootAPI to create a root API
  • Add multiple children to create siblings
Background Worker This example shows how you can use babyapi in an application alongside background workers and have runtime control over all goroutines
  • Use WithContext to add a context to an API so the API will stop when the context is cancelled
  • Use api.Done() to have other goroutines stop when the API is stopped
SQL This example shows how you can build an API with a custom implementation of babyapi.Storage using sqlc
  • Implement an Extension using a custom implementation of babyapi.Storage
  • Use api.Done() to clean up DB resources
  • Extend the built-in CLI to add flags or other customizations

Also see a full example of an application implementing a REST API using babyapi in my automated-garden project.

Contributing

Please open issues for bugs or feature requests and feel free to create a PR.

Documentation

Index

Constants

View Source
const MethodGetAll = "GetAll"

MethodGetAll is the same as http.MethodGet, but can be used when setting custom response codes

Variables

View Source
var ErrForbidden = &ErrResponse{HTTPStatusCode: http.StatusForbidden, StatusText: "Forbidden"}
View Source
var ErrMethodNotAllowedResponse = &ErrResponse{HTTPStatusCode: http.StatusMethodNotAllowed, StatusText: "Method not allowed."}
View Source
var ErrNotFound = errors.New("resource not found")
View Source
var ErrNotFoundResponse = &ErrResponse{HTTPStatusCode: http.StatusNotFound, StatusText: "Resource not found."}

Functions

func EndDatedQueryParam added in v0.13.0

func EndDatedQueryParam(value bool) url.Values

func GetIDParam

func GetIDParam(r *http.Request, name string) string

GetIDParam gets resource ID from the request URL for a resource by name

func GetLoggerFromContext

func GetLoggerFromContext(ctx context.Context) *slog.Logger

GetLoggerFromContext returns the structured logger from the context. It expects to use an HTTP request context to get a logger with details from middleware

func GetResourceFromContext

func GetResourceFromContext[T Resource](ctx context.Context, key ContextKey) (T, error)

GetResourceFromContext gets the API resource from request context

func Handler added in v0.3.0

func IDParamKey

func IDParamKey(name string) string

IDParamKey gets the chi URL param key used for the ID of a resource

func MustRenderHTML added in v0.2.0

func MustRenderHTML(tmpl *template.Template, data any) string

MustRenderHTML renders the provided template and data to a string. Panics if there is an error

func MustRenderHTMLMap added in v0.3.0

func MustRenderHTMLMap(tmpl *template.Template, tmplMap map[string]string, name string, data any) string

MustRenderHTMLMap accepts a map of template name to the template contents. It parses the template strings and executes the template with provided data. Since the template map doesn't preserve order, the name of the base/root template must be provided. A base *template.Template can be passed in to provide custom functions or already-parsed templates. Use nil if nothing is required. Panics if there is an error

func NewContextWithLogger

func NewContextWithLogger(ctx context.Context, logger *slog.Logger) context.Context

NewContextWithLogger stores a structured logger in the context

Types

type API

type API[T Resource] struct {

	// Storage is the interface used by the API server to read/write resources
	Storage[T]

	// GetAll is the handler for /base and returns an array of resources
	GetAll http.HandlerFunc

	// Get is the handler for /base/{ID} and returns a requested resource by ID
	Get http.HandlerFunc

	// Post is used to create new resources at /base
	Post http.HandlerFunc

	// Put is used to idempotently create or modify resources at /base/{ID}
	Put http.HandlerFunc

	// Patch is used to modify resources at /base/{ID}
	Patch http.HandlerFunc

	// Delete is used to delete the resource at /base/{ID}
	Delete http.HandlerFunc
	// contains filtered or unexported fields
}

API encapsulates all handlers and other pieces of code required to run the CRUID API based on the provided Resource type

func NewAPI

func NewAPI[T Resource](name, base string, instance func() T) *API[T]

NewAPI initializes an API using the provided name, base URL path, and function to create a new instance of the resource with defaults

func NewRootAPI added in v0.4.0

func NewRootAPI(name, base string) *API[*NilResource]

NewRootAPI initializes an API which can serve as a top-level parent of other APIs, so multiple unrelated resources can exist without any parent/child relationship. This API does not have any default handlers, but custom handlers can still be added. Since there are no IDs in the path, Get and GetAll routes cannot be differentiated so only Get is used

func (*API[T]) AddCustomIDRoute

func (a *API[T]) AddCustomIDRoute(method, pattern string, handler http.Handler) *API[T]

AddCustomIDRoute appends a custom API route to the base path after the ID URL parameter: /base/{ID}/custom-route. The handler for this route can access the requested resource using GetResourceFromContext

func (*API[T]) AddCustomRootRoute added in v0.3.0

func (a *API[T]) AddCustomRootRoute(method, pattern string, handler http.Handler) *API[T]

AddCustomRootRoute appends a custom API route to the absolute root path ("/"). It does not work for APIs with parents because it would conflict with the parent's route. Panics if the API is already a child when this is called

func (*API[T]) AddCustomRoute

func (a *API[T]) AddCustomRoute(method, pattern string, handler http.Handler) *API[T]

AddCustomRoute appends a custom API route to the base path: /base/custom-route

func (*API[T]) AddIDMiddleware added in v0.3.0

func (a *API[T]) AddIDMiddleware(m func(http.Handler) http.Handler) *API[T]

AddIDMiddleware adds a middleware which is active only on the paths including a resource ID

func (*API[T]) AddMiddleware added in v0.3.0

func (a *API[T]) AddMiddleware(m func(http.Handler) http.Handler) *API[T]

AddMiddleware adds a middleware which is active only on the paths without resource ID

func (*API[T]) AddNestedAPI

func (a *API[T]) AddNestedAPI(childAPI RelatedAPI) *API[T]

AddNestedAPI adds a child API to this API and initializes the parent relationship on the child's side

func (*API[T]) AddServerSentEventHandler added in v0.2.0

func (a *API[T]) AddServerSentEventHandler(pattern string) chan *ServerSentEvent

AddServerSentEventHandler is a shortcut for HandleServerSentEvents that automatically creates and returns the events channel and adds a custom handler for GET requests matching the provided pattern

func (*API[T]) AnyClient

func (a *API[T]) AnyClient(addr string) *Client[*AnyResource]

AnyClient returns a new Client based on the API's configuration. It is a shortcut for NewClient

func (*API[T]) ApplyExtension added in v0.6.0

func (a *API[T]) ApplyExtension(e Extension[T]) *API[T]

ApplyExtension adds an Extension to the API and applies it

func (*API[T]) Base

func (a *API[T]) Base() string

Base returns the API's base path

func (*API[T]) ChildAPIs added in v0.6.0

func (a *API[T]) ChildAPIs() map[string]RelatedAPI

ChildAPIs returns the nested children APIs

func (*API[T]) Client

func (a *API[T]) Client(addr string) *Client[T]

Client returns a new Client based on the API's configuration. It is a shortcut for NewClient

func (*API[T]) Command added in v0.9.0

func (a *API[T]) Command() *cobra.Command

func (*API[T]) CreateClientMap added in v0.4.0

func (a *API[T]) CreateClientMap(parent *Client[*AnyResource]) map[string]*Client[*AnyResource]

CreateClientMap returns a map of API names to the corresponding Client for that child API. This makes it easy to use child APIs dynamically. The initial parent/base client must be provided so child APIs can use NewSubClient

func (*API[T]) DefaultMiddleware added in v0.4.0

func (a *API[T]) DefaultMiddleware(r chi.Router)

func (*API[T]) Done added in v0.2.0

func (a *API[T]) Done() <-chan struct{}

Done returns a channel that's closed when the API stops, similar to context.Done()

func (*API[T]) GetFromRequest

func (a *API[T]) GetFromRequest(r *http.Request) (T, *ErrResponse)

GetFromRequest will read the API's resource type from the request body or request context

func (*API[T]) GetIDParam

func (a *API[T]) GetIDParam(r *http.Request) string

GetIDParam gets resource ID from the request URL for this API's resource

func (*API[T]) GetParentIDParam

func (a *API[T]) GetParentIDParam(r *http.Request) string

GetParentIDParam reads the URL param from the request to get the ID of the parent resource

func (*API[T]) GetRequestBodyFromContext

func (a *API[T]) GetRequestBodyFromContext(ctx context.Context) T

GetRequestBodyFromContext gets an API resource from the request context. It can only be used in URL paths that include the resource ID

func (*API[T]) GetRequestedResource

func (a *API[T]) GetRequestedResource(r *http.Request) (T, *ErrResponse)

GetRequestedResource reads the API's resource from storage based on the ID in the request URL

func (*API[T]) GetRequestedResourceAndDo

func (a *API[T]) GetRequestedResourceAndDo(do func(*http.Request, T) (render.Renderer, *ErrResponse)) http.HandlerFunc

GetRequestedResourceAndDo is a wrapper that handles getting a resource from storage based on the ID in the request URL and rendering the response. This is useful for imlementing a CustomIDRoute

func (*API[T]) GetRequestedResourceAndDoMiddleware added in v0.3.0

func (a *API[T]) GetRequestedResourceAndDoMiddleware(do func(*http.Request, T) (*http.Request, *ErrResponse)) func(next http.Handler) http.Handler

GetRequestedResourceAndDoMiddleware is a shortcut for creating an ID-scoped middleware that gets the requested resource from storage, calls the provided 'do' function, and calls next.ServeHTTP. If the resource is not found for a PUT request, the error is ignored The 'do' function returns *http.Request so the request context can be modified by middleware

func (*API[T]) GetResourceFromContext

func (a *API[T]) GetResourceFromContext(ctx context.Context) (T, error)

GetResourceFromContext gets the API resource from request context

func (*API[T]) HandleServerSentEvents added in v0.2.0

func (a *API[T]) HandleServerSentEvents(EventsBroadcastChannel *broadcastChannel[*ServerSentEvent]) http.HandlerFunc

HandleServerSentEvents is a handler function that will listen on the provided channel and write events to the HTTP response

func (*API[T]) IDParamKey

func (a *API[T]) IDParamKey() string

IDParamKey gets the chi URL param key used for this API

func (*API[T]) Name

func (a *API[T]) Name() string

Name returns the name of the API

func (*API[T]) NewContextWithRequestBody

func (a *API[T]) NewContextWithRequestBody(ctx context.Context, item T) context.Context

NewContextWithRequestBody stores the API resource in the context

func (*API[T]) Parent

func (a *API[T]) Parent() RelatedAPI

Parent returns the API's parent API

func (*API[T]) ParentContextKey

func (a *API[T]) ParentContextKey() ContextKey

ParentContextKey returns the context key for the direct parent's resource

func (*API[T]) ReadRequestBodyAndDo

func (a *API[T]) ReadRequestBodyAndDo(do func(*http.Request, T) (T, *ErrResponse)) http.HandlerFunc

ReadRequestBodyAndDo is a wrapper that handles decoding the request body into the resource type and rendering a response

func (*API[T]) Route

func (a *API[T]) Route(r chi.Router) error

Create API routes on the given router

func (*API[T]) Router

func (a *API[T]) Router() (chi.Router, error)

Create a new router with API routes

func (*API[T]) RunCLI

func (a *API[T]) RunCLI()

RunCLI is an alternative entrypoint to running the API beyond just Serve. It allows running a server or client based on the provided CLI arguments. Use this in your main() function

func (*API[T]) Serve

func (a *API[T]) Serve(address string) error

Serve will serve the API on the given port

func (*API[T]) SetAfterCreateOrUpdate added in v0.6.0

func (a *API[T]) SetAfterCreateOrUpdate(afterCreateOrUpdate func(*http.Request, T) *ErrResponse) *API[T]

func (*API[T]) SetAfterDelete

func (a *API[T]) SetAfterDelete(after func(*http.Request) *ErrResponse) *API[T]

SetAfterDelete sets a function that is executed after deleting a resource. It is useful for additional cleanup or other actions that should be done after deleting

func (*API[T]) SetBeforeDelete

func (a *API[T]) SetBeforeDelete(before func(*http.Request) *ErrResponse) *API[T]

SetBeforeDelete sets a function that is executing before deleting a resource. It is useful for additional validation before completing the delete

func (*API[T]) SetCustomResponseCode added in v0.2.0

func (a *API[T]) SetCustomResponseCode(verb string, code int) *API[T]

SetCustomResponseCode will override the default response codes for the specified HTTP verb. Use MethodGetAll to set the response code for listing all resources

func (*API[T]) SetGetAllFilter

func (a *API[T]) SetGetAllFilter(f func(*http.Request) FilterFunc[T]) *API[T]

SetGetAllFilter sets a function that can use the request context to create a filter for GetAll. Use this to introduce custom filtering after reading from storage. This should mostly be used with the default storage client options. If you are using a custom SQL or other query-based implementation, it is better to use the url.Values to create custom filtering

func (*API[T]) SetGetAllResponseWrapper added in v0.2.0

func (a *API[T]) SetGetAllResponseWrapper(getAllResponder func([]T) render.Renderer) *API[T]

SetGetAllResponseWrapper sets a function that can create a custom response for GetAll. This function will receive a slice of Resources from storage and must return a render.Renderer

func (*API[T]) SetOnCreateOrUpdate

func (a *API[T]) SetOnCreateOrUpdate(onCreateOrUpdate func(*http.Request, T) *ErrResponse) *API[T]

SetOnCreateOrUpdate runs on POST, PATCH, and PUT requests before saving the created/updated resource. This is useful for adding more validations or performing tasks related to resources such as initializing schedules or sending events

func (*API[T]) SetResponseWrapper added in v0.2.0

func (a *API[T]) SetResponseWrapper(responseWrapper func(T) render.Renderer) *API[T]

SetResponseWrapper sets a function that returns a new Renderer before responding with T. This is used to add more data to responses that isn't directly from storage

func (*API[T]) SetStorage

func (a *API[T]) SetStorage(s Storage[T]) *API[T]

func (*API[T]) Stop

func (a *API[T]) Stop()

Stop will stop the API

func (*API[T]) WithContext added in v0.9.0

func (a *API[T]) WithContext(ctx context.Context) *API[T]

WithContext adds a context to the API so that it will automatically shutdown when the context is closed

type AnyResource

type AnyResource map[string]any

AnyResource is intended to create a "generic" Client

func (*AnyResource) Bind

func (dr *AnyResource) Bind(r *http.Request) error

func (AnyResource) GetID

func (ar AnyResource) GetID() string

func (*AnyResource) Render

type BuilderError added in v0.6.0

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

BuilderError is used for combining errors that may occur when constructing a new API

func (BuilderError) Error added in v0.6.0

func (e BuilderError) Error() string

type Client

type Client[T Resource] struct {
	Address string
	// contains filtered or unexported fields
}

Client is used to interact with the provided Resource's API

func NewClient

func NewClient[T Resource](addr, base string) *Client[T]

NewClient initializes a Client for interacting with the Resource API

func NewSubClient

func NewSubClient[T, R Resource](parent *Client[T], path string) *Client[R]

NewSubClient creates a Client as a child of an existing Client. This is useful for accessing nested API resources

func (*Client[T]) Command added in v0.9.0

func (c *Client[T]) Command(name string, input *cliArgs) *cobra.Command

func (*Client[T]) Delete

func (c *Client[T]) Delete(ctx context.Context, id string, parentIDs ...string) (*Response[T], error)

Delete makes a DELETE request to delete a resource by ID

func (*Client[T]) DeleteRequest added in v0.11.0

func (c *Client[T]) DeleteRequest(ctx context.Context, id string, parentIDs ...string) (*http.Request, error)

DeleteRequest creates a request that can be used to delete a resource

func (*Client[T]) Get

func (c *Client[T]) Get(ctx context.Context, id string, parentIDs ...string) (*Response[T], error)

Get will get a resource by ID

func (*Client[T]) GetAll

func (c *Client[T]) GetAll(ctx context.Context, rawQuery string, parentIDs ...string) (*Response[*ResourceList[T]], error)

GetAll gets all resources from the API

func (*Client[T]) GetAllAny added in v0.12.0

func (c *Client[T]) GetAllAny(ctx context.Context, rawQuery string, parentIDs ...string) (*Response[any], error)

GetAllAny allows using GetAll when using a custom response wrapper

func (*Client[T]) GetAllRequest added in v0.11.0

func (c *Client[T]) GetAllRequest(ctx context.Context, rawQuery string, parentIDs ...string) (*http.Request, error)

GetAllRequest creates a request that can be used to get all resources

func (*Client[T]) GetRequest added in v0.11.0

func (c *Client[T]) GetRequest(ctx context.Context, id string, parentIDs ...string) (*http.Request, error)

GetRequest creates a request that can be used to get a resource

func (*Client[T]) MakeGenericRequest added in v0.11.0

func (c *Client[T]) MakeGenericRequest(req *http.Request, target any) error

MakeGenericRequest allows making a request without specifying the return type. It accepts a pointer receiver to pass to json.Unmarshal. This allows returning any type using the Client.

func (*Client[T]) MakeRequest

func (c *Client[T]) MakeRequest(req *http.Request, expectedStatusCode int) (*Response[T], error)

MakeRequest generically sends an HTTP request after calling the request editor and checks the response code It returns a babyapi.Response which contains the http.Response after extracting the body to Body string and JSON decoding the resource type into Data if the response is JSON

func (*Client[T]) NewRequestWithParentIDs

func (c *Client[T]) NewRequestWithParentIDs(ctx context.Context, method string, body io.Reader, id string, parentIDs ...string) (*http.Request, error)

NewRequestWithParentIDs uses http.NewRequestWithContext to create a new request using the URL created from the provided ID and parent IDs

func (*Client[T]) Patch

func (c *Client[T]) Patch(ctx context.Context, id string, resource T, parentIDs ...string) (*Response[T], error)

Patch makes a PATCH request to modify a resource by ID

func (*Client[T]) PatchRaw

func (c *Client[T]) PatchRaw(ctx context.Context, id, body string, parentIDs ...string) (*Response[T], error)

PatchRaw makes a PATCH request to modify a resource by ID. It uses the provided string as the request body

func (*Client[T]) PatchRequest added in v0.11.0

func (c *Client[T]) PatchRequest(ctx context.Context, body io.Reader, id string, parentIDs ...string) (*http.Request, error)

PatchRequest creates a request that can be used to PATCH a resource

func (*Client[T]) Post

func (c *Client[T]) Post(ctx context.Context, resource T, parentIDs ...string) (*Response[T], error)

Post makes a POST request to create a new resource

func (*Client[T]) PostRaw

func (c *Client[T]) PostRaw(ctx context.Context, body string, parentIDs ...string) (*Response[T], error)

PostRaw makes a POST request using the provided string as the body

func (*Client[T]) PostRequest added in v0.11.0

func (c *Client[T]) PostRequest(ctx context.Context, body io.Reader, parentIDs ...string) (*http.Request, error)

PostRequest creates a request that can be used to POST a resource

func (*Client[T]) Put

func (c *Client[T]) Put(ctx context.Context, resource T, parentIDs ...string) (*Response[T], error)

Put makes a PUT request to create/modify a resource by ID

func (*Client[T]) PutRaw

func (c *Client[T]) PutRaw(ctx context.Context, id, body string, parentIDs ...string) (*Response[T], error)

PutRaw makes a PUT request to create/modify a resource by ID. It uses the provided string as the request body

func (*Client[T]) PutRequest added in v0.11.0

func (c *Client[T]) PutRequest(ctx context.Context, body io.Reader, id string, parentIDs ...string) (*http.Request, error)

PutRequest creates a request that can be used to PUT a resource

func (*Client[T]) SetCustomResponseCode added in v0.4.0

func (c *Client[T]) SetCustomResponseCode(verb string, code int) *Client[T]

SetCustomResponseCode will override the default expected response codes for the specified HTTP verb

func (*Client[T]) SetCustomResponseCodeMap added in v0.4.0

func (c *Client[T]) SetCustomResponseCodeMap(customResponseCodes map[string]int) *Client[T]

SetCustomResponseCodeMap sets the whole map for custom expected response codes

func (*Client[T]) SetHTTPClient

func (c *Client[T]) SetHTTPClient(client *http.Client) *Client[T]

SetHTTPClient allows overriding the Clients HTTP client with a custom one

func (*Client[T]) SetRequestEditor

func (c *Client[T]) SetRequestEditor(requestEditor RequestEditor) *Client[T]

SetRequestEditor sets a request editor function that is used to modify all requests before sending. This is useful for adding custom request headers or authorization

func (*Client[T]) URL

func (c *Client[T]) URL(id string, parentIDs ...string) (string, error)

URL gets the URL based on provided ID and optional parent IDs

type ContextKey

type ContextKey string

ContextKey is used to store API resources in the request context

type DefaultRenderer added in v0.3.0

type DefaultRenderer struct{}

DefaultRenderer implements an empty Render method and can be used to easily create render.Renderer implementations without having to add the method

func (*DefaultRenderer) Render added in v0.3.0

type DefaultResource

type DefaultResource struct {
	*DefaultRenderer

	ID ID `json:"id"`
}

DefaultResource implements Resource and uses the provided ID type. Extending this type is the easiest way to implement a Resource based around the provided ID type

func NewDefaultResource

func NewDefaultResource() DefaultResource

NewDefaultResource creates a DefaultResource with a new random ID

func (*DefaultResource) Bind

func (dr *DefaultResource) Bind(r *http.Request) error

func (*DefaultResource) GetID

func (dr *DefaultResource) GetID() string

type EndDateable added in v0.13.0

type EndDateable interface {
	EndDated() bool
	SetEndDate(time.Time)
}

EndDateable allows soft-delete by setting an end-date on resources instead of deleting them

type ErrResponse

type ErrResponse struct {
	Err            error `json:"-"`
	HTTPStatusCode int   `json:"-"`

	StatusText string `json:"status"`          // user-level status message
	AppCode    int64  `json:"code,omitempty"`  // application-specific error code
	ErrorText  string `json:"error,omitempty"` // application-level error message, for debugging
}

ErrResponse is an error that implements Renderer to be used in HTTP response

func ErrInvalidRequest

func ErrInvalidRequest(err error) *ErrResponse

func ErrRender

func ErrRender(err error) *ErrResponse

func InternalServerError

func InternalServerError(err error) *ErrResponse

func (*ErrResponse) Error

func (e *ErrResponse) Error() string

func (*ErrResponse) Render

func (e *ErrResponse) Render(_ http.ResponseWriter, r *http.Request) error

type Extension added in v0.6.0

type Extension[T Resource] interface {
	Apply(*API[T]) error
}

Extension is a way that a collection of modifiers, or other code, can be applied to an API all at once. This makes code more reusable and allows external libraries to provide modifiers

type FilterFunc

type FilterFunc[T any] func(T) bool

FilterFunc is used for GetAll to filter resources that are read from storage

func (FilterFunc[T]) Filter added in v0.12.0

func (f FilterFunc[T]) Filter(in []T) []T

type HTMLer added in v0.2.0

type HTMLer interface {
	HTML(*http.Request) string
}

HTMLer allows for easily represending reponses as HTML strings when accepted content type is text/html

type ID

type ID struct {
	xid.ID
}

ID is a type that can be optionally used to improve Resources and their APIs. It uses xid to create unique identifiers and implements a custom Bind method to:

  • Disallow POST requests with IDs
  • Automatically set new ID on POSTed resources
  • Enforce that ID is set
  • Do not allow changing ID with PATCH

func NewID

func NewID() ID

func (*ID) Bind

func (id *ID) Bind(r *http.Request) error

type KVStorage added in v0.13.0

type KVStorage[T Resource] struct {
	// contains filtered or unexported fields
}

KVStorage implements the Storage interface for the provided type using hord.Database for the storage backend

It allows soft-deleting if your type implements the kv.EndDateable interface. This means Delete will set the end-date to now and update in storage instead of deleting. If something is already end-dated, then it is hard-deleted. Also, the GetAll method will automatically read the 'end_dated' query param to determine if end-dated resources should be filtered out

func (*KVStorage[T]) Delete added in v0.13.0

func (c *KVStorage[T]) Delete(ctx context.Context, id string) error

Delete will delete a resource by the key. If the resource implements EndDateable, it will first soft-delete by setting the EndDate to time.Now()

func (*KVStorage[T]) Get added in v0.13.0

func (c *KVStorage[T]) Get(_ context.Context, id string) (T, error)

Get will use the provided key to read data from the data source. Then, it will Unmarshal into the generic type

func (*KVStorage[T]) GetAll added in v0.13.0

func (c *KVStorage[T]) GetAll(_ context.Context, query url.Values) ([]T, error)

GetAll will use the provided prefix to read data from the data source. Then, it will use Get to read each element into the correct type

func (*KVStorage[T]) Set added in v0.13.0

func (c *KVStorage[T]) Set(_ context.Context, item T) error

Set marshals the provided item and writes it to the database

type NilResource added in v0.4.0

type NilResource struct{ *DefaultRenderer }

NilResource is an empty resource type which should be used when creating APIs without any real resource

func (*NilResource) Bind added in v0.4.0

func (*NilResource) Bind(r *http.Request) error

func (*NilResource) GetID added in v0.4.0

func (*NilResource) GetID() string

type Patcher

type Patcher[T Resource] interface {
	Patch(T) *ErrResponse
}

Patcher is used to optionally-enable PATCH endpoint. Since the library cannot generically modify resources without using reflection, implement Patch function to use the input to modify the receiver

type PrintableResponse added in v0.5.0

type PrintableResponse interface {
	Fprint(out io.Writer, pretty bool) error
}

PrintableResponse allows CLI method to generically return a type that can be written to out

type RelatedAPI added in v0.2.0

type RelatedAPI interface {
	Router() (chi.Router, error)
	Route(chi.Router) error
	Base() string
	Name() string
	GetIDParam(*http.Request) string
	Parent() RelatedAPI
	CreateClientMap(*Client[*AnyResource]) map[string]*Client[*AnyResource]
}

RelatedAPI declares a subset of methods from the API struct that are required to enable nested/parent-child API relationships

type RequestEditor

type RequestEditor = func(*http.Request) error

RequestEditor is a function that can modify the HTTP request before sending

var DefaultRequestEditor RequestEditor = func(r *http.Request) error {
	return nil
}

type Resource

type Resource interface {
	comparable
	// Renderer is used to control the output behavior when creating a response.
	// Use this for any after-request logic or response modifications
	render.Renderer

	// Binder is used to control the input behavior, after decoding the request.
	// Use it for input validation or additional modification of the resource using request headers or other params
	render.Binder

	GetID() string
}

Resource is an interface/constraint used for API resources. In order to use API, you must have types that implement this. It enables HTTP request/response handling and getting resources by ID

type ResourceList

type ResourceList[T render.Renderer] struct {
	Items []T `json:"items"`
}

ResourceList is used to automatically enable the GetAll endpoint that returns an array of Resources

func (*ResourceList[T]) Render

func (rl *ResourceList[T]) Render(w http.ResponseWriter, r *http.Request) error

type Response added in v0.2.0

type Response[T any] struct {
	ContentType string
	Body        string
	Data        T
	Response    *http.Response
}

Response wraps an HTTP response from the API and allows easy access to the decoded response type (if JSON), the ContentType, string Body, and the original response

func MakeRequest added in v0.2.0

func MakeRequest[T any](req *http.Request, client *http.Client, expectedStatusCode int, requestEditor RequestEditor) (*Response[T], error)

MakeRequest generically sends an HTTP request after calling the request editor and checks the response code It returns a babyapi.Response which contains the http.Response after extracting the body to Body string and JSON decoding the resource type into Data if the response is JSON

func (*Response[T]) Fprint added in v0.2.0

func (sr *Response[T]) Fprint(out io.Writer, pretty bool) error

Fprint writes the Response body to the provided Writer. If the ContentType is JSON, it will JSON encode the body. Setting pretty=true will print indented JSON.

type ServerSentEvent added in v0.2.0

type ServerSentEvent struct {
	Event string
	Data  string
}

ServerSentEvent is a simple struct that represents an event used in HTTP event stream

func (*ServerSentEvent) Write added in v0.2.0

func (sse *ServerSentEvent) Write(w http.ResponseWriter)

Write will write the ServerSentEvent to the HTTP response stream and flush. It removes all newlines in the event data

type Storage

type Storage[T Resource] interface {
	// Get a single resource by ID
	Get(context.Context, string) (T, error)
	// GetAll will return all resources that match the provided query filters
	GetAll(context.Context, url.Values) ([]T, error)
	// Set will save the provided resource
	Set(context.Context, T) error
	// Delete will delete a resource by ID
	Delete(context.Context, string) error
}

Storage defines how the API will interact with a storage backend

func NewKVStorage added in v0.13.0

func NewKVStorage[T Resource](db hord.Database, prefix string) Storage[T]

NewKVStorage creates a new storage client for the specified type. It stores resources with keys prefixed by 'prefix'

Directories

Path Synopsis
cmd
examples
storage
kv

Jump to

Keyboard shortcuts

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