krest

package module
v0.0.6 Latest Latest
Warning

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

Go to latest
Published: Nov 29, 2023 License: Unlicense Imports: 12 Imported by: 5

README

Welcome to KRest

KRest stands for Keep it simple REST Package.

It's a very simple and powerful package wrapper over the standard http package for making requests in an easier and less verbose way.

The interface

Krest aims to be simple and so its interface is very simple and we don't plan on adding new functions to it if we can avoid it:

// Provider describes the functions necessary to do all types of REST
// requests.
//
// It returns error if it was not possible to complete the request
// or if the status code of the request was not in the range 200-299.
type Provider interface {
	Get(ctx context.Context, url string, data RequestData) (resp Response, err error)
	Post(ctx context.Context, url string, data RequestData) (resp Response, err error)
	Put(ctx context.Context, url string, data RequestData) (resp Response, err error)
	Patch(ctx context.Context, url string, data RequestData) (resp Response, err error)
	Delete(ctx context.Context, url string, data RequestData) (resp Response, err error)
}

Sample requests

Simple requests using krest look like this:

package examples

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"strconv"
	"time"

	"github.com/vingarcia/krest"
)

type User struct {
	Name    string  `json:"name"`
	Age     int     `json:"age"`
	Address Address `json:"address"`
}

type Address struct {
	City    string `json:"city"`
	Country string `json:"country"`
}

func main() {
	// Build the client with a maximum request timeout limit of 2 seconds
	// You may specify a shorter timeout on each request using the context.
	rest := krest.New(2 * time.Second)
	ctx := context.Background()

	user, err := getUser(ctx, rest)
	if err != nil {
		log.Fatalf("unable to get user: %s", err)
	}

	err = sendUser(ctx, rest, user)
	if err != nil {
		log.Fatalf("unable to send user: %s", err)
	}
}

func getUser(ctx context.Context, rest krest.Provider) (User, error) {
	resp, err := rest.Get(ctx, "https://example.com/user", krest.RequestData{})
	if err != nil {
		// An error is returned for any status not in range 200-299,
		// and it is safe to use the `resp` value even when there are errors.
		if resp.StatusCode == 404 {
			log.Fatalf("example.com was not found!")
		}
		// The error message contains all the information you'll need to understand
		// the error, such as Method, Request URL, response status code and even
		// the raw Payload from the error response:
		log.Fatalf("unexpected error when fetching example.com: %s", err)
	}

	// Using intermediary structs for decoding payloads like this one
	// is recomended for decoupling your internal models from the external
	// payloads:
	var parsedUser struct {
		Name    string  `json:"name"`
		Age     string  `json:"age"`
		Address Address `json:"address"`
	}
	err = json.Unmarshal(resp.Body, &parsedUser)
	if err != nil {
		return User{}, fmt.Errorf("unable to parse example user response as JSON: %s", err)
	}

	// Decode the age that was passed as string to an internal
	// format that is easier to manipulate:
	age, _ := strconv.Atoi(parsedUser.Age)

	return User{
		Name:    parsedUser.Name,
		Age:     age,
		Address: parsedUser.Address,
	}, nil
}

func sendUser(ctx context.Context, rest krest.Provider, user User) error {
	_, err := rest.Post(ctx, "https://other.example.com", krest.RequestData{
		Headers: map[string]string{
			"Authorization": "Bearer some-valid-jwt-token-goes-here",
		},

		// Using the optional retry feature:
		MaxRetries: 3,

		// Again using intermediary structs (or in this case a map) is also recommended
		// for encoding messages to match other APIs so you can keep your internal models
		// decoupled from any external dependencies:
		Body: map[string]interface{}{
			"fullname": user.Name,
			"address":  user.Address,
		},
	})
	if err != nil {
		// Again this error message will already contain the info you might need to debug
		// but it is always a good idea to add more information when available:
		return fmt.Errorf("error sending user to example.com: %s", err)
	}

	return nil
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultRandMillis

func DefaultRandMillis() time.Duration

DefaultRandMillis calculates retry random factor based on the following guide:

https://cloud.google.com/iot/docs/how-tos/exponential-backoff

func DefaultRetryRule

func DefaultRetryRule(resp *http.Response, err error) bool

DefaultRetryRule is the default retry rule that will retry (i.e. return true) if the request ends with an error, if the status is > 500 or if the status is one of: StatusLocked, StatusTooEarly and StatusTooManyRequests.

func MultipartFile

func MultipartFile(data io.Reader, name string) io.Reader

MultipartFile is a helper for encoding files as a part of the multipart data payload

func MultipartItem

func MultipartItem(data io.Reader, contentType string) io.Reader

MultipartItem is a helper for encoding items with specific content-types as a part of the multipart data payload

func Retry

func Retry(ctx context.Context, baseDelay time.Duration, maxDelay time.Duration, maxRetries int, fn func() bool)

Retry retries the fn callback using an exponential backoff strategy starting from `baseDelay` and performing at most `maxRetries`

A retry is attempted only when the callback returns true.

Types

type Client

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

Client contains methods for making rest requests these methods accept any struct that can be marshaled into JSON but the response is returned in Bytes, since not all APIs follow rest strictly.

func New

func New(timeout time.Duration, middlewares ...Middleware) Client

New instantiates a new rest client

func (*Client) AddMiddleware added in v0.0.3

func (c *Client) AddMiddleware(middlewares ...Middleware)

AddMiddleware adds one or more new middlewares to this instance

func (Client) Delete

func (c Client) Delete(ctx context.Context, url string, data RequestData) (Response, error)

Delete will make a DELETE request to the input URL and return the results

func (Client) Get

func (c Client) Get(ctx context.Context, url string, data RequestData) (Response, error)

Get will make a GET request to the input URL and return the results

func (Client) Patch

func (c Client) Patch(ctx context.Context, url string, data RequestData) (Response, error)

Patch will make a PATCH request to the input URL and return the results

func (Client) Post

func (c Client) Post(ctx context.Context, url string, data RequestData) (Response, error)

Post will make a POST request to the input URL and return the results

func (Client) Put

func (c Client) Put(ctx context.Context, url string, data RequestData) (Response, error)

Put will make a PUT request to the input URL and return the results

type Middleware added in v0.0.3

type Middleware func(
	ctx context.Context,
	method string,
	url string,
	data RequestData,
	next NextMiddleware,
) (Response, error)

Middleware describes the expected format for this package middlewares

type Mock

type Mock struct {
	GetFn    func(ctx context.Context, url string, data RequestData) (resp Response, err error)
	PostFn   func(ctx context.Context, url string, data RequestData) (resp Response, err error)
	PutFn    func(ctx context.Context, url string, data RequestData) (resp Response, err error)
	PatchFn  func(ctx context.Context, url string, data RequestData) (resp Response, err error)
	DeleteFn func(ctx context.Context, url string, data RequestData) (resp Response, err error)
}

Mock mocks the krest.Provider interface with a configurable structure

func (Mock) Delete

func (m Mock) Delete(ctx context.Context, url string, data RequestData) (resp Response, err error)

Delete mocks the krest.Provider.Delete method

func (Mock) Get

func (m Mock) Get(ctx context.Context, url string, data RequestData) (resp Response, err error)

Get mocks the krest.Provider.Get method

func (Mock) Patch

func (m Mock) Patch(ctx context.Context, url string, data RequestData) (resp Response, err error)

Patch mocks the krest.Provider.Patch method

func (Mock) Post

func (m Mock) Post(ctx context.Context, url string, data RequestData) (resp Response, err error)

Post mocks the krest.Provider.Post method

func (Mock) Put

func (m Mock) Put(ctx context.Context, url string, data RequestData) (resp Response, err error)

Put mocks the krest.Provider.Put method

type MultipartData

type MultipartData = map[string]io.Reader

MultipartData is a helper type for storing the multipart data payload in a practical structure.

type NextMiddleware added in v0.0.3

type NextMiddleware func(
	ctx context.Context,
	method string,
	url string,
	data RequestData,
) (Response, error)

NextMiddleware describes the signature of the `next` middleware function

type Provider

type Provider interface {
	Get(ctx context.Context, url string, data RequestData) (resp Response, err error)
	Post(ctx context.Context, url string, data RequestData) (resp Response, err error)
	Put(ctx context.Context, url string, data RequestData) (resp Response, err error)
	Patch(ctx context.Context, url string, data RequestData) (resp Response, err error)
	Delete(ctx context.Context, url string, data RequestData) (resp Response, err error)
}

Provider describes the functions necessary to do all types of REST requests.

It returns error if it was not possible to complete the request or if the status code of the request was not in the range 200-299.

type RequestData

type RequestData struct {
	// The body accepts any struct that can
	// be marshaled into JSON
	Body interface{}

	Headers map[string]any

	// It's the max number of retries, if 0 it defaults 1
	MaxRetries int

	// The start and max delay for the exponential backoff strategy
	// if unset they default to 300ms and 32s respectively
	BaseRetryDelay time.Duration
	MaxRetryDelay  time.Duration

	// Set this attribute if you want to personalize the retry behavior
	// if nil it defaults to `rest.DefaultRetryRule()`
	RetryRule func(resp *http.Response, err error) bool

	// Use this for setting up mutual TLS
	TLSConfig *tls.Config

	// Set this option to true if you
	// expect to receive big bodies of data
	// and you don't want this library to
	// to load all of it in memory.
	//
	// When using this option the resp.Body
	// field will be set to null and you'll
	// need to use the response struct as an io.ReadCloser
	// for streaming the data wherever you need to.
	//
	// Don't forget to close it afterwards.
	//
	// Also note that there is no need to call resp.Close()
	// if you are not using the Stream option or if the call
	// returns an error.
	Stream bool
}

RequestData describes the optional arguments for all the http methods of this client.

func (*RequestData) SetDefaultsIfNecessary

func (r *RequestData) SetDefaultsIfNecessary()

SetDefaultsIfNecessary sets the default values for the RequestData structure

type Response

type Response struct {
	io.ReadCloser

	Body       []byte
	Header     http.Header
	StatusCode int
}

Response describes the expected attributes on the response for a REST request

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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