mason

package module
v0.0.0-...-0609f45 Latest Latest
Warning

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

Go to latest
Published: Jun 16, 2025 License: MIT Imports: 14 Imported by: 0

README

Mason

Mason is a JSON schema-backed API framework for writing HTTP handlers in Go. It offers input validation and decoding and generates an OpenAPI 3.1 spec. By design, it has a small API, so it can be adopted by teams with an existing codebase.

Background

It was created to serve the API (v2) at MagicBell, and guided by the following design goals:

  • JSON schema first - The Input/Output models are described by JSON schema, with an example. By implementing the model.Entity interface, the model definition is tested against the schema so they are never out of sync.
  • Incremental adoption - Mason should be easy to add to an existing project, by giving it a mason.Runtime implementation that can Handle the Operation created by Mason, and Respond to a HTTP request.
  • Support Resource grouping & querying - REST API resources and endpoints are a map to an API/product's feature offerings. For example /integrations/slack, and /integrations/web_push are two different resources, but to get all integration resources, the integration RouteGroup comes in handy.

Usage

Add it to your project:

  go get github.com/magicbell/mason@latest

You'll need a Runtime implementation to start using Mason in your existing project, but for new projects, or to kick the tires, you can use the [HTTPRuntime](runtime.go).

  api := mason.NewAPI(mason.NewHTTPRuntime())

##* GET Handler

Let's add a new GET /ping endpoint that returns the current timestamp. To do this, we need to define the output struct.

  var _ model.Entity = (*Response)(nil)

  type Response struct {
    Timestamp time.Time `json:"timestamp"`
  }

  func (r *Response) Example() []byte {
    return []byte(`{
      "timestamp": "2023-10-01T12:00:00Z"
    }`)
  }

  func (r *Response) Marshal() (json.RawMessage, error) {
    return json.Marshal(r)
  }

  func (r *Response) Name() string {
    return "PingResponse"
  }

  func (r *Response) Schema() []byte {
    return []byte(`{
      "type": "object",
      "properties": {
        "timestamp": {
          "type": "string",
          "format": "date-time"
        }
      },
      "required": ["timestamp"]
    }`)
  }

  func (r *Response) Unmarshal(data json.RawMessage) error {
    return json.Unmarshal(data, r)
  }

Now we can use it to define the Handler (note that we use model.Nil for decoding query params, which signals that we don't care about them. Since this is GET request, there is no input struct, but model.Nil can also be used for POST/PUT handlers that accept no request body.

  func PingHandler(ctx context.Context, r *http.Request, params model.Nil) (rsp *Response, err error) {
    return &Response{
      Timestamp: time.Now().UTC(),
    }, nil
  }

Create a new RouteGroup

  api.NewRouteGroup("ping")

Register the Handler

	grp.Register(mason.HandleGet(PingHandler).
		Path("/ping").
		WithOpID("ping").
		WithSummary("Ping the server").
		WithDesc("Ping the server when you are unsure of the time"))

You can try this example by running example/ping/main.go. The example also mounts a handler to serve the OpenAPI file.

	gen, err := openapi.NewGenerator(api)
	if err != nil {
		panic(fmt.Errorf("failed to create OpenAPI generator: %w", err))
	}
	gen.Spec.Info.WithTitle("Ping API")

	schema, err := gen.Schema()
	if err != nil {
		panic(fmt.Errorf("failed to generate OpenAPI schema: %w", err))
	}

	// We can mix mason endpoints, with standard HTTP handlers
	rtm.Handle("GET", "/openapi.json", func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		w.Header().Set("Content-Type", "application/json")
		if _, err := w.Write(schema); err != nil {
			return fmt.Errorf("failed to write OpenAPI schema: %w", err)
		}

		return nil
	})
POST Handler

Let's move on to more the more exciting stuff, and build a small counter API. Let's add a POST handler that will increment the counter by 1, or by an increment, which is an optional field in the request body. Once again, we start by defining a model that confirms to the platform.Entity interface.


var _ model.Entity = (*Input)(nil)

// Input Model
type Input struct {
	Increment *int `json:"increment"`
}

// Example implements model.Entity.
func (r *Input) Example() []byte {
	return []byte(`{
		"increment": 5
	}`)
}

func (r *Input) Marshal() (json.RawMessage, error) {
	return json.Marshal(r)
}

func (r *Input) Name() string {
	return "IncrementInput"
}

func (r *Input) Schema() []byte {
	return []byte(`{
		"type": "object",
		"properties": {
			"timestamp": {
				"type": ["integer", "null"]
			}
		}
	}`)
}

func (r *Input) Unmarshal(data json.RawMessage) error {
	return json.Unmarshal(data, r)
}

Let's define the CountResponse as the Output model for the POST (as well as the GET) handler.

// Output Model
var _ model.Entity = (*Response)(nil)

type Response struct {
	Count int `json:"count"`
}

// Example implements model.Entity.
func (r *Response) Example() []byte {
	return []byte(`{
		"count": 5
	}`)
}

func (r *Response) Marshal() (json.RawMessage, error) {
	return json.Marshal(r)
}

func (r *Response) Name() string {
	return "CountResponse"
}

func (r *Response) Schema() []byte {
	return []byte(`{
		"type": "object",
		"properties": {
			"count": {
				"type": "integer"
			}
		},
		"required": ["count"]
	}`)
}

func (r *Response) Unmarshal(data json.RawMessage) error {
	return json.Unmarshal(data, r)
}

Finally, we can define the POST handler, and accept the validated and decoded Input model in our code.

  var count int

  func IncrementHandler(ctx context.Context, r *http.Request, inp *Input, params model.Nil) (rsp *Response, err error) {
    inc := 1
    if inp.Increment != nil {
      inc = *inp.Increment
    }
    count += inc

    return &Response{
      Count: count,
    }, nil
  }

Registering the handler on the route group

	rtm := mason.NewHTTPRuntime()
	api := mason.NewAPI(rtm)
	grp := api.NewRouteGroup("counter")

	grp.Register(mason.HandlePost(IncrementHandler).
		Path("/increment").
		WithOpID("increment").
		WithSummary("Increment the counter").
		WithDesc("Increment the counter by one, or the supplied increment"))

The code for this example is in example/counter/main.go, and it contains a GET handler, as well the route for grabbing the OpenAPI file.

Let's start counting!

# --data sends a POST request with curl
curl http://localhost:9090/increment \
  --header 'Content-Type: application/json' \
  --data '{}'
{"count":1}

Let's increment by 2

curl http://localhost:9090/increment \
  --header 'Content-Type: application/json' \
  --data '{"increment": 2}'
{"count":3}

How about some invalid input?

# -v to see the response code and body
curl -v http://localhost:9090/increment \
  --header 'Content-Type: application/json' \
  --data '{"increment": "2"}'

* upload completely sent off: 18 bytes
< HTTP/1.1 422 Unprocessable Entity
< Content-Type: application/json
< Date: Fri, 13 Jun 2025 08:02:05 GMT
< Content-Length: 91
<
{"errors":[{"error":null,"message":"Param 'increment' should be of type [integer,null]"}]}

The Respond method handles the error formatting in the HTTPRuntime by checking if the error is of type model.ValidationError. This code can be the starting point for returning formatted errors to your users, in your Runtime implementation, or a middleware.

  func (r *HTTPRuntime) Handle(method string, path string, handler WebHandler, mws ...func(WebHandler) WebHandler) {
    r.HandleFunc(fmt.Sprintf("%s %s", method, path), func(w http.ResponseWriter, req *http.Request) {
      if req.Method != method {
        http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
        return
      }

      ctx := req.Context()
      if err := handler(ctx, w, req); err != nil {
        // Format validation Error
        var fe model.ValidationError
        if errors.As(err, &fe) {
          // Return well-formatted validation errors
          if err := r.Respond(ctx, w, fe, http.StatusUnprocessableEntity); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
          }

          return
        }

        http.Error(w, err.Error(), http.StatusInternalServerError)
      }
    })
  }

Schema Registry

Mason collects the schema of every I/O model registered via the Handlers in a registry. This allows for resolving schema references, and other goodies!

Schema Dereference

To illustrate the derefeencing, take a look at the example/schemaexample/main.go, which recreates the POST /increment handler from the counter example, but this time, returns a server key in the response. The server key contains a timestamp, and we also add a GET /healthcheck endpoint that returns the same key in it's response.

First, let's setup the GET /healthcheck output model and handler.

  var _ model.Entity = (*Response)(nil)

  type HealthResponse struct {
    Timestamp time.Time `json:"timestamp"`
  }

  // Example implements model.Entity.
  func (r *HealthResponse) Example() []byte {
    return []byte(`{
      "timestamp": "2023-10-01T12:00:00Z"
    }`)
  }

  func (r *HealthResponse) Marshal() (json.RawMessage, error) {
    return json.Marshal(r)
  }

  func (r *HealthResponse) Name() string {
    return "HealthResponse"
  }

  func (r *HealthResponse) Schema() []byte {
    return []byte(`{
      "type": "object",
      "properties": {
        "timestamp": {
          "type": "string",
          "format": "date-time"
        }
      },
      "required": ["timestamp"]
    }`)
  }

  func (r *HealthResponse) Unmarshal(data json.RawMessage) error {
    return json.Unmarshal(data, r)
  }

  func HealthCheckHandler(ctx context.Context, r *http.Request, params model.Nil) (rsp *HealthResponse, err error) {
    return &HealthResponse{
      Timestamp: time.Now().UTC(),
    }, nil
  }

Is the API healthy? Let's find out!

➜  ext git:(chore/mason-update) ✗ curl http://localhost:9090/healthcheck
{"timestamp":"2025-06-13T09:26:31.615403Z"}

We can use the schema contributed by the HealthResponse, and identified by it's Name() in the Response model for the POST /increment handler.

  type Response struct {
    Count  int            `json:"count"`
    Server HealthResponse `json:"server"`
  }


  func (r *Response) Schema() []byte {
    return []byte(`{
      "type": "object",
      "properties": {
        "count": {
          "type": "integer"
        },
        "server": {
          "$ref": "#/components/schemas/HealthResponse"
        }
      },
      "required": ["count"]
    }`)
  }

Let's increment the counter!

curl http://localhost:9090/increment \
  --header 'Content-Type: application/json' \
  --data '{"increment": 2}'
{"count":2,"server":{"timestamp":"2025-06-13T09:25:37.031658Z"}}

The generated OpenAPI schema uses the registry references, and if we were composing an input model, the dereferenced schema would also be used for validation.

...
  "paths": {
      "/healthcheck": {
        "get": {
          "summary": "Get the server status",
          "description": "",
          "operationId": "healthcheck",
          "responses": {
            "200": {
              "description": "OK",
              "content": {
                "application/json": {
                  "schema": {
                    "$ref": "#/components/schemas/HealthResponse"
                  }
                }
              }
            }
          }
        }
      },
      "/increment": {
        "post": {
          "summary": "Increment the counter",
          "description": "Increment the counter by one, or the supplied increment",
          "operationId": "increment",
          "requestBody": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IncrementInput"
                }
              }
            }
          },
          "responses": {
            "201": {
              "description": "Created",
              "content": {
                "application/json": {
                  "schema": {
                    "$ref": "#/components/schemas/CountResponse"
                  }
                }
              }
            }
          },
          "x-forbid-unknown-cookie": true,
          "x-forbid-unknown-header": true,
          "x-forbid-unknown-path": true,
          "x-forbid-unknown-query": true
        }
      }
    },
    "components": {
      "schemas": {
        "CountResponse": {
          "examples": [
            {
              "count": 5
            }
          ],
...

If you need the dereferenced schema, you can grab it from the api instance.

	sch, err := api.DereferenceSchema(schema_with_references)
Model, Schema, and Example Sync

The sync package can check if the model's struct, schema, and example are in sync.

Development Status

As mentioned in the intro, Mason is in active development and usage at MagicBell. In open-sourcing it, we want to give back to the incredible Go community, and also receive feedback, contributions, and ideas for improvements.

Plesae create issues with your questions, and if you use Mason, we'd love to hear from you, too!

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecodeQueryParams

func DecodeQueryParams[Q any](r *http.Request) (Q, error)

func DecodeRequest

func DecodeRequest[T model.Entity](api *API, r *http.Request, opts ...DecodeOption) (ent T, err error)

func DefaultSuccessCode

func DefaultSuccessCode(method string, output m.WithSchema) int

func RecursivelyUnwrap

func RecursivelyUnwrap(current m.WithSchema) m.WithSchema

Types

type API

type API struct {
	Runtime
	// contains filtered or unexported fields
}

func NewAPI

func NewAPI(runtime Runtime) *API

func (*API) DereferenceSchema

func (a *API) DereferenceSchema(schema []byte) ([]byte, error)

func (*API) GetModel

func (a *API) GetModel(name string) (model.Entity, bool)

func (*API) GetOperation

func (a *API) GetOperation(method string, path string) (Operation, bool)

func (*API) HasOperation

func (a *API) HasOperation(method string, path string) bool

func (*API) NewRouteGroup

func (a *API) NewRouteGroup(name string) *RouteGroup

func (*API) Operations

func (a *API) Operations() []Operation

func (*API) Registry

func (a *API) Registry() Registry

type AuthType

type AuthType string

type Builder

type Builder interface {
	ResourceID() string
	OpID() string
	Path(p string) Builder
	WithGroup(group string) Builder
	WithOpID(segments ...string) Builder
	WithDesc(d string) Builder
	WithTags(tags ...string) Builder
	WithSuccessCode(code int) Builder
	WithSummary(s string) Builder
	WithMWs(mw ...Middleware) Builder
	WithExtensions(key string, val interface{}) Builder
	SkipIf(skip bool) Builder
	RegisterBeta(api *API)
	Register(api *API)
}

type DecodeOption

type DecodeOption func(options *decodeOptions) error

type HTTPRuntime

type HTTPRuntime struct {
	*http.ServeMux
}

func NewHTTPRuntime

func NewHTTPRuntime() *HTTPRuntime

func (*HTTPRuntime) Handle

func (r *HTTPRuntime) Handle(method string, path string, handler WebHandler, mws ...func(WebHandler) WebHandler)

func (*HTTPRuntime) Respond

func (r *HTTPRuntime) Respond(ctx context.Context, w http.ResponseWriter, data any, status int) error

type HandlerNoBody

type HandlerNoBody[O model.Entity, Q any] func(ctx context.Context, r *http.Request, params Q) (response O, err error)

type HandlerWithBody

type HandlerWithBody[T model.Entity, O model.Entity, Q any] func(ctx context.Context, r *http.Request, model T, params Q) (response O, err error)

type Middleware

type Middleware interface {
	GetHandler(builder Builder) func(WebHandler) WebHandler
}

type Model

type Model struct {
	jsonschema.Struct
	model.WithSchema
}

func NewModel

func NewModel(ent model.WithSchema) Model

func (Model) IsNil

func (m Model) IsNil() bool

func (Model) JSONSchema

func (m Model) JSONSchema() (jsonschema.Schema, error)

type Operation

type Operation struct {
	OperationID string                 `json:"operationID,omitempty"`
	Input       model.Entity           `json:"input,omitempty"`
	Output      model.Entity           `json:"output,omitempty"`
	Method      string                 `json:"method,omitempty"`
	Path        string                 `json:"path,omitempty"`
	QueryParams any                    `json:"queryParams,omitempty"`
	Description string                 `json:"description,omitempty"`
	Summary     string                 `json:"summary,omitempty"`
	SuccessCode int                    `json:"code,omitempty"`
	Tags        []string               `json:"tags,omitempty"`
	Extensions  map[string]interface{} `json:"mapOfAnything,omitempty"`
}

type Option

type Option func(*Operation)

func WithDescription

func WithDescription(desc string) Option

func WithExtension

func WithExtension(val map[string]interface{}) Option

func WithOperationID

func WithOperationID(opID string) Option

func WithSuccessCode

func WithSuccessCode(code int) Option

func WithSummary

func WithSummary(summary string) Option

func WithTags

func WithTags(tags ...string) Option

type Registry

type Registry map[string]Resource

func (*Registry) Endpoints

func (mgm *Registry) Endpoints(transform func(string) string) []string

func (*Registry) FindOp

func (mgm *Registry) FindOp(method string, path string) (Operation, bool)

func (*Registry) Ops

func (mgm *Registry) Ops() []Operation

func (*Registry) TaggedOps

func (mgm *Registry) TaggedOps(tags ...string) []Operation

TaggedOps returns all models that have all the tags provided

type Resource

type Resource map[string]Operation

func (*Resource) FirstOp

func (mg *Resource) FirstOp() (Operation, bool)

type RouteBuilderBase

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

type RouteBuilderNoBody

type RouteBuilderNoBody[T m.Entity, Q any] struct {
	RouteBuilderBase
	// contains filtered or unexported fields
}

func HandleGet

func HandleGet[T model.Entity, Q any](handler HandlerNoBody[T, Q]) *RouteBuilderNoBody[T, Q]

func (*RouteBuilderNoBody[T, Q]) OpID

func (rb *RouteBuilderNoBody[T, Q]) OpID() string

OpID returns the operation ID for the route.

func (*RouteBuilderNoBody[T, Q]) Path

func (rb *RouteBuilderNoBody[T, Q]) Path(p string) Builder

Path sets the path for the route. This can include path parameters like /users/{id}

func (*RouteBuilderNoBody[T, Q]) Register

func (rb *RouteBuilderNoBody[T, Q]) Register(api *API)

Register registers the route with the mux, and finalizes the route configuration.

func (*RouteBuilderNoBody[T, Q]) RegisterBeta

func (rb *RouteBuilderNoBody[T, Q]) RegisterBeta(api *API)

RegisterBeta registers the route and marks it as beta, meaning it will not be included in the OpenAPI documentation.

func (*RouteBuilderNoBody[T, Q]) ResourceID

func (rb *RouteBuilderNoBody[T, Q]) ResourceID() string

func (*RouteBuilderNoBody[T, Q]) SkipIf

func (rb *RouteBuilderNoBody[T, Q]) SkipIf(skip bool) Builder

SkipIf ensures that the route is not documented if the condition is true.

func (*RouteBuilderNoBody[T, Q]) WithDesc

func (rb *RouteBuilderNoBody[T, Q]) WithDesc(d string) Builder

WithDesc sets the description for the route. This is used primarily for documentation purposes.

func (*RouteBuilderNoBody[T, Q]) WithExtensions

func (rb *RouteBuilderNoBody[T, Q]) WithExtensions(key string, val interface{}) Builder

WithExtensions sets custom x- attributes for the route. This is used for adding OpenAPI extensions..

func (*RouteBuilderNoBody[T, Q]) WithGroup

func (rb *RouteBuilderNoBody[T, Q]) WithGroup(group string) Builder

func (*RouteBuilderNoBody[T, Q]) WithMWs

func (rb *RouteBuilderNoBody[T, Q]) WithMWs(mw ...Middleware) Builder

WithMWs defines a set of middlewares to add to the route.

func (*RouteBuilderNoBody[T, Q]) WithOpID

func (rb *RouteBuilderNoBody[T, Q]) WithOpID(id ...string) Builder

WithOpID sets the operationID for the route. This is used primarily for documentation purposes.

func (*RouteBuilderNoBody[T, Q]) WithSuccessCode

func (rb *RouteBuilderNoBody[T, Q]) WithSuccessCode(code int) Builder

WithSuccessCode sets the success code for the route. This can be used to override the default success code for the method.

func (*RouteBuilderNoBody[T, Q]) WithSummary

func (rb *RouteBuilderNoBody[T, Q]) WithSummary(s string) Builder

func (*RouteBuilderNoBody[T, Q]) WithTags

func (rb *RouteBuilderNoBody[T, Q]) WithTags(tags ...string) Builder

WithTags sets the tags for the route. This is used primarily for documentation purposes.

type RouteBuilderWithBody

type RouteBuilderWithBody[T m.Entity, O m.Entity, Q any] struct {
	RouteBuilderBase
	// contains filtered or unexported fields
}

func HandleDelete

func HandleDelete[T model.Entity, O model.Entity, Q any](handler HandlerWithBody[T, O, Q]) *RouteBuilderWithBody[T, O, Q]

func HandlePatch

func HandlePatch[T model.Entity, O model.Entity, Q any](handler HandlerWithBody[T, O, Q]) *RouteBuilderWithBody[T, O, Q]

func HandlePost

func HandlePost[T model.Entity, O model.Entity, Q any](handler HandlerWithBody[T, O, Q]) *RouteBuilderWithBody[T, O, Q]

func HandlePut

func HandlePut[T model.Entity, O model.Entity, Q any](handler HandlerWithBody[T, O, Q]) *RouteBuilderWithBody[T, O, Q]

func (*RouteBuilderWithBody[T, O, Q]) OpID

func (rb *RouteBuilderWithBody[T, O, Q]) OpID() string

OpID returns the operation ID for the route.

func (*RouteBuilderWithBody[T, O, Q]) Path

func (rb *RouteBuilderWithBody[T, O, Q]) Path(p string) Builder

Path sets the path for the route. This can include path parameters like /users/{id}

func (*RouteBuilderWithBody[T, O, Q]) Register

func (rb *RouteBuilderWithBody[T, O, Q]) Register(api *API)

Register registers the route with the mux, and finalizes the route configuration.

func (*RouteBuilderWithBody[T, O, Q]) RegisterBeta

func (rb *RouteBuilderWithBody[T, O, Q]) RegisterBeta(api *API)

RegisterBeta registers the route and marks it as beta, meaning it will not be included in the OpenAPI documentation.

func (*RouteBuilderWithBody[T, O, Q]) ResourceID

func (rb *RouteBuilderWithBody[T, O, Q]) ResourceID() string

ResourceID returns the resource ID for the route.

func (*RouteBuilderWithBody[T, O, Q]) SkipIf

func (rb *RouteBuilderWithBody[T, O, Q]) SkipIf(skip bool) Builder

SkipIf ensures that the route is not documented if the condition is true.

func (*RouteBuilderWithBody[T, O, Q]) WithDesc

func (rb *RouteBuilderWithBody[T, O, Q]) WithDesc(d string) Builder

WithDesc sets the description for the route. This is used primarily for documentation purposes.

func (*RouteBuilderWithBody[T, O, Q]) WithExtensions

func (rb *RouteBuilderWithBody[T, O, Q]) WithExtensions(key string, val interface{}) Builder

WithExtensions sets custom x- attributes for the route. This is used for adding OpenAPI extensions..

func (*RouteBuilderWithBody[T, O, Q]) WithGroup

func (rb *RouteBuilderWithBody[T, O, Q]) WithGroup(group string) Builder

func (*RouteBuilderWithBody[T, O, Q]) WithMWs

func (rb *RouteBuilderWithBody[T, O, Q]) WithMWs(mw ...Middleware) Builder

WithMWs defines a set of middlewares to add to the route.

func (*RouteBuilderWithBody[T, O, Q]) WithOpID

func (rb *RouteBuilderWithBody[T, O, Q]) WithOpID(id ...string) Builder

WithOpID sets the operationID for the route. This is used primarily for documentation purposes.

func (*RouteBuilderWithBody[T, O, Q]) WithSuccessCode

func (rb *RouteBuilderWithBody[T, O, Q]) WithSuccessCode(code int) Builder

WithSuccessCode sets the success code for the route. This can be used to override the default success code for the method.

func (*RouteBuilderWithBody[T, O, Q]) WithSummary

func (rb *RouteBuilderWithBody[T, O, Q]) WithSummary(s string) Builder

func (*RouteBuilderWithBody[T, O, Q]) WithTags

func (rb *RouteBuilderWithBody[T, O, Q]) WithTags(tags ...string) Builder

WithTags sets the tags for the route. This is used primarily for documentation purposes.

type RouteGroup

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

func (*RouteGroup) FullPath

func (g *RouteGroup) FullPath() string

func (*RouteGroup) Name

func (g *RouteGroup) Name() string

func (*RouteGroup) NewRouteGroup

func (g *RouteGroup) NewRouteGroup(name string) *RouteGroup

func (*RouteGroup) Register

func (g *RouteGroup) Register(builder Builder)

func (*RouteGroup) SkipRESTValidation

func (g *RouteGroup) SkipRESTValidation(name string) *RouteGroup

SkipRESTValidation relaxes the constraint that all routes in a group must handle the same resource.

type RouteHandler

type RouteHandler interface {
	Handle(method string, path string, handler WebHandler, mws ...func(WebHandler) WebHandler)
}

type Runtime

type Runtime interface {
	RouteHandler
	WebResponder
}

type WebHandler

type WebHandler func(ctx context.Context, w http.ResponseWriter, r *http.Request) error

type WebResponder

type WebResponder interface {
	Respond(ctx context.Context, w http.ResponseWriter, data any, status int) error
}

Directories

Path Synopsis
example
internal
Package model contains the data models for the Mason API.
Package model contains the data models for the Mason API.
sync
Package sync provides utilities for vetting models against their declared schemas.
Package sync provides utilities for vetting models against their declared schemas.

Jump to

Keyboard shortcuts

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