sinatra

package module
v0.0.0-...-374d363 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 4 Imported by: 0

README

go-ruby-sinatra/sinatra

sinatra — go-ruby-sinatra

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of the deterministic core of Ruby's Sinatra (Sinatra 4.x) — the route table, the Mustermann-style pattern compiler, the request dispatcher (before/after filters, halt/pass/redirect, not_found/error handlers) and the assembly of the Rack [status, headers, body] tuple — matching the MRI sinatra gem, without any Ruby runtime.

It is built on go-ruby-rack, reusing rack.Request to read the incoming environment and rack.Response to shape the outgoing tuple, and it is a sibling of the other pure-Go Ruby front-ends (go-ruby-regexp, go-ruby-erb, go-ruby-yaml). It is the Sinatra backend for go-embedded-ruby.

What it is — and isn't. Compiling a route pattern, extracting params (named captures, splats, the query merge), ordering the filters and the halt/pass control flow are fully deterministic and need no interpreter, so they live here as pure Go. The route action bodies (the Ruby do…end blocks), the session store, and template rendering (ERB/Haml) are seams the embedding runtime (rbgo) supplies. The HTTP server — the socket accept loop, TLS, Rack::Handler — is the host's job and is out of scope.

Features

Validated against the sinatra gem (4.x) via a differential MRI oracle:

  • Routing — a route table per HTTP verb (Get/Post/Put/Delete/Patch/ Options/Head, plus Route for arbitrary verbs). Get also registers HEAD, exactly like Sinatra.
  • Pattern compilation — Mustermann-style "/hello/:name" named captures, * splats, the optional ?, and raw regexp routes, compiled to an anchored regexp with the exact :name → [^/?#]+, :name? → (…)?, * → .*? semantics. URI-decoded captures; multiple splats accumulate into the params["splat"] array; regexp captures feed params["captures"].
  • Params merge — request (query + form) params seed params, then route captures override on collision via Sinatra's v2 || v1 rule (a nil capture keeps the query value; an absent optional :name? still yields the key).
  • Dispatch — first-match-wins ordering, before/after filters (with their own patterns and capture merge), halt/pass/redirect/status/ content_type/headers/body, and the not_found/error(code) handlers, producing the Rack [status, headers, body] tuple.
  • HelpersParams/Request/Response/Session shaping, content_type mime resolution with Sinatra's add_charset rule, url/uri resolution, conditional halt, and a set/enable/disable settings registry.

Out of scope (a noted seam): templating (ERB/Haml) — rbgo's ERB compiler can layer on top.

CGO-free, 100% test coverage, gofmt + go vet clean, race-clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x) and three OSes (Linux, macOS, Windows).

Install

go get github.com/go-ruby-sinatra/sinatra

Usage

package main

import (
	"strings"

	"github.com/go-ruby-rack/rack"
	"github.com/go-ruby-sinatra/sinatra"
)

func main() {
	app := sinatra.New()

	app.Get("/hello/:name", func(c *sinatra.Context) any {
		return "Hello, " + c.ParamString("name") + "!"
	})

	app.Get("/say/*/to/*", func(c *sinatra.Context) any {
		v, _ := c.Param("splat") // []string{"hello", "world"}
		return strings.Join(v.([]string), " ")
	})

	app.Before("/admin/*", func(c *sinatra.Context) any {
		c.Halt(401, "unauthorized")
		return nil
	})

	app.Get("/old", func(c *sinatra.Context) any {
		c.Redirect("/new") // 302, absolute Location
		return nil
	})

	app.NotFound(func(c *sinatra.Context) any { return "nothing here" })

	// Serve a Rack env; a host HTTP server feeds the env and writes the tuple.
	status, headers, body := app.CallTuple(rack.Env{
		rack.RequestMethod: "GET",
		rack.PathInfo:      "/hello/world",
		rack.ServerName:    "localhost",
		rack.ServerPort:    "9292",
		rack.RackURLScheme: "http",
	})
	_ = status
	_ = headers
	_ = body
}
The action-body / session / template seams

A route or filter body is a sinatra.Actionfunc(*sinatra.Context) any. The embedding runtime (rbgo) binds a Ruby do…end block to an Action; a Go caller supplies one directly. The returned value is coerced to the response body (string, []string, []byte, an int status, or nil to keep a body set via c.Body/c.Halt). The control-flow helpers c.Halt, c.Pass and c.Redirect unwind the action immediately, exactly like Sinatra's throw :halt/:pass.

The session store is reached through c.Session() (the rack.session env value the host populates); this library only shapes access to it. Templating is not included — rbgo's ERB compiler renders templates and feeds the result back as the action body.

Tests & coverage

GOWORK=off go test -race -cover ./...

The differential oracle compiles the same routes under the real sinatra gem and compares the route→params, filter order, halt/pass/redirect, content_type and the Rack tuple byte-for-byte. It skips itself where Ruby or the gem is absent (Windows, the cross-arch qemu lanes); the deterministic, Ruby-free suite alone holds coverage at 100%.

License

BSD-3-Clause © the go-ruby-sinatra/sinatra authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package sinatra is a pure-Go (no cgo) reimplementation of the deterministic core of Ruby's Sinatra web framework (Sinatra 4.x) — the route table, the Mustermann-style pattern compiler, the request dispatcher (before/after filters, halt/pass/redirect, not_found/error handlers) and the assembly of the Rack [status, headers, body] tuple — matching the MRI sinatra gem.

It is built on github.com/go-ruby-rack/rack, reusing rack.Request to read the incoming environment and rack.Response to shape the outgoing tuple, and it is a sibling of the other go-ruby-* front-ends (go-ruby-regexp, go-ruby-erb, go-ruby-yaml, go-ruby-rack).

What it is — and isn't

Compiling a route pattern to a matcher, extracting params (named captures, splats, query merge), ordering the filters, and the halt/pass control flow are all fully deterministic and need no interpreter, so they live here as pure Go. The action bodies (the `do…end` blocks of a Ruby route), the session store, and template rendering (ERB/Haml) are SEAMS the embedding runtime (go-embedded-ruby's rbgo) supplies. The HTTP server — the socket accept loop, TLS, Rack::Handler — is the host's job and is out of scope.

An action body is modelled as an Action: a Go func given a *Context that returns a body (or invokes Halt/Pass/Redirect on the context). rbgo binds a Ruby block to an Action; a Go caller can supply one directly.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func MimeType

func MimeType(sym string) string

MimeType resolves a Sinatra mime symbol or extension to a media type, like Sinatra::Base.mime_type. A value already containing a "/" is returned as is (Sinatra treats a full media type as itself). It returns "" when unknown.

func StatusText

func StatusText(code int) string

StatusText returns the canonical reason phrase for an HTTP status code, from rack's status table (e.g. 404 -> "Not Found"). It is "" for an unknown code.

Types

type Action

type Action func(c *Context) any

Action is the body of a route (or a filter): the Ruby `do…end` block as a Go func. It receives the live *Context and returns the body. The embedding runtime (rbgo) binds a Ruby block to an Action; a Go caller supplies one directly. Control-flow helpers — Halt, Pass, Redirect — are invoked on the context and unwind via panic, exactly like Sinatra's throw :halt/:pass.

type Context

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

Context carries the per-request dispatch state and exposes the Sinatra DSL helpers an action body uses: Params, Request, Response, Status, Body, ContentType, Headers, Redirect, Halt and Pass. It wraps go-ruby-rack's Request and Response.

func (*Context) App

func (c *Context) App() *Sinatra

App returns the owning application (for settings access).

func (*Context) Body

func (c *Context) Body(s string)

Body sets the response body to a single string, like the `body` helper.

func (*Context) ContentType

func (c *Context) ContentType(typeArg string)

ContentType sets the Content-Type, resolving a Sinatra mime symbol/extension and appending a charset per Sinatra's add_charset rule, like the `content_type` helper. It panics (HaltError 500-style) for an unknown type, matching Sinatra raising on an unknown media type — callers using rbgo see the raise; Go callers should pass known types or a full media type.

func (*Context) ContentTypeCharset

func (c *Context) ContentTypeCharset(typeArg, charset string)

ContentTypeCharset is Context.ContentType with an explicit charset (content_type :json, charset: 'utf-8').

func (*Context) CurrentStatus

func (c *Context) CurrentStatus() int

CurrentStatus returns the response status.

func (*Context) Halt

func (c *Context) Halt(args ...any)

Halt stops processing immediately, like Sinatra's `halt`. With no argument it keeps the current status; an int sets the status; a string sets the body; (int, string) sets both. Extra/!typed arguments are ignored.

func (*Context) Header

func (c *Context) Header(key string, value string)

Header sets a response header, like response.headers[k]=v.

func (*Context) Headers

func (c *Context) Headers(h map[string]string)

Headers sets several response headers at once, like the `headers` helper.

func (*Context) Param

func (c *Context) Param(key string) (any, bool)

Param returns the named param value and whether it is present. The value is a string for a scalar, or a []string for "splat"/"captures".

func (*Context) ParamKeys

func (c *Context) ParamKeys() []string

ParamKeys returns the param keys in insertion order.

func (*Context) ParamString

func (c *Context) ParamString(key string) string

ParamString returns the string value of a scalar param, or "" if absent or not a string.

func (*Context) Params

func (c *Context) Params() []ParamPair

Params returns a snapshot of the params as an ordered key/value list, preserving Sinatra's deterministic ordering (route params, then splat, then query). The values are strings or []string.

func (*Context) Pass

func (c *Context) Pass()

Pass abandons the current route and tells the dispatcher to try the next matching route for the request method, like Sinatra's `pass`. If no further route matches, the not_found handler runs.

func (*Context) Redirect

func (c *Context) Redirect(target string, status ...int)

Redirect performs a Sinatra redirect: it sets the Location header (resolving a relative target against the request URL) and halts with status 302 (or the given status). Like Sinatra, it unwinds the action immediately.

func (*Context) Request

func (c *Context) Request() *rack.Request

Request returns the underlying rack.Request.

func (*Context) Response

func (c *Context) Response() *rack.Response

Response returns the underlying rack.Response being assembled.

func (*Context) Session

func (c *Context) Session() any

Session returns the session store seam (the rack.session env value). The store itself is supplied by the host; Sinatra only shapes access to it.

func (*Context) Settings

func (c *Context) Settings() *Settings

Settings returns the application settings, like the `settings` helper.

func (*Context) Status

func (c *Context) Status(code int)

Status sets the response status, like the `status` helper.

func (*Context) URI

func (c *Context) URI(path string) string

URI builds an absolute URI for path, like Sinatra's url/uri helper. With an absolute target it is returned unchanged.

type MatchResult

type MatchResult struct {
	// Named maps each :name token to its URI-decoded captured value, in the
	// order the tokens appear. A nil capture (an absent optional :name?) is
	// omitted, matching Sinatra dropping the key.
	Named *orderedParams
	// Splat is the ordered list of URI-decoded "*" captures (or, for a regexp
	// pattern, its numbered captures).
	Splat []string
	// FromRegexp reports whether Splat came from a raw regexp pattern, so the
	// dispatcher knows to file it under params["captures"] not params["splat"].
	FromRegexp bool
}

MatchResult holds the outcome of matching a path against a Pattern.

type ParamPair

type ParamPair struct {
	Key   string
	Value any
}

ParamPair is one key/value entry of the params hash.

type Pattern

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

Pattern is a compiled Sinatra route pattern. It is produced by Compile from a Mustermann-style string ("/hello/:name", "/say/*/to/*", "/posts/:id?") or wrapped around a raw regexp by CompileRegexp.

Matching a path yields the ordered named captures and the ordered list of splat ("*") values, mirroring how Sinatra populates params: a :name token becomes params["name"], every * (and a regexp group) feeds the params["splat"] / params["captures"] array, and all captured values are URI-decoded.

func Compile

func Compile(pat string) *Pattern

Compile turns a Mustermann-style Sinatra pattern into a Pattern. The grammar handled is the Sinatra default: ":name" named captures, "*" splats, a trailing "?" making the preceding token optional, and literal text (with regexp metacharacters escaped). It mirrors Mustermann's compiled regexp: :name -> (?P<n>[^/?#]+), :name? -> (?:(?P<n>[^/?#]+))? and * -> (.*?), all anchored with \A…\z.

Every token in the grammar emits a valid regexp fragment — names are [A-Za-z0-9_], literals are quoted, and the structural pieces are fixed — so the assembled regexp always compiles; Compile therefore never returns a pattern error. A raw user regexp, which can be malformed, goes through CompileRegexp instead.

func CompileRegexp

func CompileRegexp(src string) (*Pattern, error)

CompileRegexp wraps a raw regexp source as a Pattern. Its numbered captures feed params["captures"], matching Sinatra's `get %r{…}` form. The source is anchored with \A…\z if not already.

func (*Pattern) Match

func (p *Pattern) Match(path string) (MatchResult, bool)

Match tests path against the pattern. It returns the extracted captures and true on a match, or a zero MatchResult and false otherwise. Captured values are URI-decoded (Mustermann/Sinatra decode each segment).

func (*Pattern) String

func (p *Pattern) String() string

String returns the compiled regexp source, useful for debugging.

type Settings

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

Settings is the per-application registry behind Sinatra's set/enable/disable. Values are arbitrary; the dispatcher reads a handful of well-known keys (default_content_type, default_encoding). It is insertion-ordered so a caller can enumerate settings deterministically.

func (*Settings) Bool

func (s *Settings) Bool(key string) bool

Bool returns the boolean value of key, or false if unset or non-bool. It mirrors Sinatra treating a truthy setting (used by enable?/disable?).

func (*Settings) Disable

func (s *Settings) Disable(key string)

Disable sets key to false (Sinatra's `disable`).

func (*Settings) Enable

func (s *Settings) Enable(key string)

Enable sets key to true (Sinatra's `enable`).

func (*Settings) Get

func (s *Settings) Get(key string) (any, bool)

Get returns the value for key and whether it was set.

func (*Settings) Keys

func (s *Settings) Keys() []string

Keys returns the setting keys in insertion order (a copy).

func (*Settings) Set

func (s *Settings) Set(key string, val any)

Set assigns key to val (Sinatra's `set`), appending new keys in order.

func (*Settings) String

func (s *Settings) String(key string) string

String returns the string value of key, or "" if unset or non-string.

type Sinatra

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

Sinatra is an application: the route table per HTTP verb, the before/after filters, the not_found and error handlers, and the settings registry. Build one with New, register routes with Get/Post/…, then serve a Rack env with Sinatra.Call.

func New

func New() *Sinatra

New returns an empty application with Sinatra's defaults.

func (*Sinatra) After

func (s *Sinatra) After(pat string, action Action)

After registers an after-filter, run after the action (and after a halt).

func (*Sinatra) Before

func (s *Sinatra) Before(pat string, action Action)

Before registers a before-filter. With an empty pattern it runs on every request; otherwise only when pat matches the path (and its captures merge into params, like Sinatra).

func (*Sinatra) Call

func (s *Sinatra) Call(env rack.Env) *rack.Response

Call serves a Rack environment, returning the [status, headers, body] tuple as a rack.Response. It runs the before filters, finds the first matching route for the request method (honouring pass), runs the action, runs the after filters, and applies the not_found / error handlers.

func (*Sinatra) CallTuple

func (s *Sinatra) CallTuple(env rack.Env) (int, *rack.Headers, []string)

CallTuple is a convenience returning the SPEC [status, headers, body] tuple directly, the form a Rack server consumes.

func (*Sinatra) Delete

func (s *Sinatra) Delete(pat string, action Action)

Delete registers a DELETE route.

func (*Sinatra) Error

func (s *Sinatra) Error(status int, action Action)

Error registers a handler for a specific HTTP status (Sinatra's error(code)).

func (*Sinatra) Get

func (s *Sinatra) Get(pat string, action Action)

Get registers a GET (and, by Sinatra convention, HEAD) route.

func (*Sinatra) Head

func (s *Sinatra) Head(pat string, action Action)

Head registers a HEAD route explicitly.

func (*Sinatra) NotFound

func (s *Sinatra) NotFound(action Action)

NotFound registers the handler for an unmatched request (Sinatra's not_found), run with status 404.

func (*Sinatra) Options

func (s *Sinatra) Options(pat string, action Action)

Options registers an OPTIONS route.

func (*Sinatra) Patch

func (s *Sinatra) Patch(pat string, action Action)

Patch registers a PATCH route.

func (*Sinatra) Post

func (s *Sinatra) Post(pat string, action Action)

Post registers a POST route.

func (*Sinatra) Put

func (s *Sinatra) Put(pat string, action Action)

Put registers a PUT route.

func (*Sinatra) Route

func (s *Sinatra) Route(verb, pat string, action Action)

Route registers an action for an arbitrary HTTP verb and Mustermann pattern.

func (*Sinatra) RoutePattern

func (s *Sinatra) RoutePattern(verb string, p *Pattern, action Action)

RoutePattern registers an action for verb against an already-compiled Pattern (e.g. a regexp route from CompileRegexp).

func (*Sinatra) Settings

func (s *Sinatra) Settings() *Settings

Settings returns the application settings registry (set/enable/disable).

type UnknownMediaType

type UnknownMediaType struct{ Type string }

UnknownMediaType is raised by ContentType for an unrecognised media type, mirroring Sinatra's "Unknown media type" error.

func (*UnknownMediaType) Error

func (e *UnknownMediaType) Error() string

Jump to

Keyboard shortcuts

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