/optic

A simplified, generic, entity based web library for golang that's drop in compatible with net/http

Primary LanguageGoGNU General Public License v3.0GPL-3.0

(o)ptic

A generic web extension to net/http

Optic helps you call backend functions from your frontend by sending a regular go struct and recieving a struct back

It is especially useful when making requests to a go service from a go client (WASM app, cli, tui ...)

Install

go get github.com/nanvenomous/optic

Then import with

import (
	"github.com/nanvenomous/optic"
)

Quick example

Define the entities and error interface

type solution struct {
	Answer int
}
type division struct {
	Top    int
	Bottom int
}
type userHTTPError struct {
	Message string
	Code    int
}
func (e *userHTTPError) GetCode() int {
	return e.Code
}

Setup the service route

func divide(recieved *division, _ *http.Request) (*solution, optic.HTTPError) {
	if recieved.Bottom == 0 { // return an error
		return nil, &userHTTPError{Code: http.StatusUnprocessableEntity, Message: "Impossible to divide by Zero"}
	}
	return &solution{Answer: recieved.Top / recieved.Bottom}, nil
}

func main() {
	var (
		err       error
		encodeErr = &userHTTPError{Code: http.StatusInternalServerError, Message: "Failed to encode your response."}
		decodeErr = &userHTTPError{Code: http.StatusNotAcceptable, Message: "Failed to decode your request body."}
		mux       *http.ServeMux
	)
	mux = http.NewServeMux()
	optic.SetupService(port, userOpticRoute, encodeErr, decodeErr, mux)
	// An optical mirror simply recieves information and sends information back
	optic.Mirror(divide) // by default optic will use function name as route
	err = optic.Serve() // run the service
}

Setup the client and make a request

func main {
	optic.SetupClient(host, port, userOpticRoute, false)
	// Make requests
	var (
		err     error          // internal error
		httpErr *userHTTPError // service exception
		sln     solution       // output
	)
	//                                                     send                          receive
	httpErr, err = optic.Glance[userHTTPError]("/divide/", &division{Top: 4, Bottom: 2}, &sln)
	fmt.Println(err, httpErr) // <nil> <nil>
	fmt.Println(sln.Answer)   // 2
}

net/http compatibility

Optic is drop in compatible with net/http

Give optic a *http.ServerMux & a special route where it will handle all you functions

Then do whatever else you want with that mux

func main() {
	var (
		err error
		mux *http.ServeMux
	)
	mux = http.NewServeMux()
	optic.SetupService(port, userOpticRoute, encodeErr, decodeErr, mux)

	// Add other routes not handled by optic, as you would with any net/http service
	mux.HandleFunc("/health-check/", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

    // optic can register middleware for you
	optic.RegisterMiddleware(exampleMiddleware)
    // or you can do it yourself
    var (
        handler http.Handler
    )
    handler = exampleMiddleware(mux)
}

func exampleMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// add some middleware (like CORS for example)
		w.Header().Set("Access-Control-Allow-Origin", "*")
		next.ServeHTTP(w, r)
	})
}

Examples

For the full example in code see ./examples/main.go

Run the example like so: run example

Community

I am planning some outreach so I can get feedback from other go developers & aiming to address major concerns between each post

Simplicity

optic/go.mod

Lines 1 to 3 in b8a94eb

module github.com/nanvenomous/optic
go 1.20

Inspiration

I drew some inspiration from leptos server functions