bindcore

package
v0.5.11 Latest Latest
Warning

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

Go to latest
Published: Aug 16, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package bindcore holds the declarations both transport runtimes need and neither should own: the error model, the uploaded-file value, and the check helpers. Keeping them here is what lets an error cross between the net/http and fasthttp surfaces and still match, and what keeps a model struct naming File from pulling a transport in behind it.

It imports no transport package.

Index

Constants

View Source
const (
	DefaultSocketReadLimit    int64 = 1 << 20
	DefaultSocketIdleTimeout        = 60 * time.Second
	DefaultSocketPingInterval       = 54 * time.Second
	DefaultSocketWriteTimeout       = 10 * time.Second
	DefaultSocketBufferSize         = 4096
)

Socket option defaults. Every one is non-zero: an unset limit is an allocation the peer chooses, and an unset read deadline is a read nothing can interrupt under TinyGo.

View Source
const DefaultMaxMultipartBodyBytes int64 = 1 << 20

DefaultMaxMultipartBodyBytes is the default cap on multipart request bodies (1 MiB). Override with SetMaxMultipartBodyBytes.

View Source
const DefaultMultipartMaxMemory int64 = 32 << 20

DefaultMultipartMaxMemory is how much of a multipart form stays in RAM before file parts spill to temp files. This is not a body size cap; see DefaultMaxMultipartBodyBytes.

View Source
const ProblemContentType = "application/problem+json"

ProblemContentType is the media type of an RFC 9457 error document.

Variables

View Source
var (
	ErrNilSocket    = errors.New("httpbind: nil socket")
	ErrSocketClosed = errors.New("httpbind: socket closed")
	// ErrBinaryMessage reports a binary frame on a socket carrying JSON.
	ErrBinaryMessage = errors.New("httpbind: binary message on a JSON socket")
)
View Source
var (
	ErrNilStream    = errors.New("httpbind: nil stream")
	ErrStreamClosed = errors.New("httpbind: stream closed")
)
View Source
var ErrFileTooLarge = errors.New("httpbind: multipart file too large")

ErrFileTooLarge is returned when a single file part exceeds the limit.

View Source
var ErrNoHijacker = errors.New("httpbind: ResponseWriter is not an http.Hijacker; serve through tinygodriver/httpserver")

ErrNoHijacker reports a ResponseWriter that cannot hand over the connection, which under TinyGo means the server is not the one an upgrade needs.

Functions

func AppendFileJSON

func AppendFileJSON(dst []byte, f File) []byte

AppendFileJSON appends an uploaded file the way encoding/json rendered it before generated encoders stopped going through reflection: exported fields in declaration order, with the content base64-encoded.

func BadRequest

func BadRequest(problem Problem, cause ...error) error

BadRequest returns a 400 Bad Request error.

func BindError

func BindError(field, location, message string) error

BindError is returned when binding fails for a specific field/source.

func CheckDate

func CheckDate(s string) bool

CheckDate reports whether s is an ISO date (YYYY-MM-DD / time.DateOnly).

func CheckDateTime

func CheckDateTime(s string) bool

CheckDateTime reports whether s is RFC3339 (fractional seconds accepted).

func CheckEmail

func CheckEmail(s string) bool

CheckEmail reports whether s is a pragmatic (non-RFC5322) email. Empty string returns false; callers skip empty optional fields before calling.

func CheckOriginDefault added in v0.5.4

func CheckOriginDefault(origin, host string) bool

CheckOriginDefault refuses a handshake whose Origin names a host other than the one the request was addressed to, and admits one carrying no Origin.

A socket accepting any origin is cross-site request forgery with a persistent connection, so this is the default rather than the opt-in.

func CheckTime

func CheckTime(s string) bool

CheckTime reports whether s is an ISO time (HH:MM:SS / time.TimeOnly).

func CheckUUID

func CheckUUID(s string) bool

CheckUUID reports whether s is a UUID string (8-4-4-4-12 hex with dashes). Version/variant bits are not enforced.

func Conflict

func Conflict(problem Problem, cause ...error) error

Conflict returns a 409 Conflict error.

func Forbidden

func Forbidden(problem Problem, cause ...error) error

Forbidden returns a 403 Forbidden error.

func Internal

func Internal(err error) error

Internal returns a 500 Internal Server Error that wraps err.

func IsJSONMediaType

func IsJSONMediaType(media string) bool

IsJSONMediaType reports whether media is JSON or a +json structured syntax suffix type (RFC 6839), e.g. application/json, application/problem+json, application/vnd.api+json. text/json is also accepted.

func IsMessageTooLarge

func IsMessageTooLarge(err error) bool

IsMessageTooLarge reports the transport-neutral size-limit failures, without errors.As so TinyGo does not need reflect.AssignableTo. A caller adds its own transport's error type before consulting this.

func MaxMultipartBodyBytes

func MaxMultipartBodyBytes() int64

MaxMultipartBodyBytes returns the effective global multipart body limit.

func MediaType

func MediaType(ct string) string

MediaType returns the lowercase type/subtype of a Content-Type header value (parameters after ';' are stripped).

func MultipartParseError

func MultipartParseError(err error, tooLarge bool) error

MultipartParseError maps a parse failure to 413 or 400. The caller decides tooLarge, because detecting it needs a transport-specific error type on top of the shared cases IsMessageTooLarge covers.

func NotFound

func NotFound(problem Problem, cause ...error) error

NotFound returns a 404 Not Found error.

func PayloadTooLarge

func PayloadTooLarge(problem Problem, cause ...error) error

PayloadTooLarge returns a 413 Payload Too Large error.

func ProblemResponse

func ProblemResponse(err error) (status int, body []byte, ok bool)

ProblemResponse derives the status and body of the RFC 9457 document for err. ok is false when err is nil and nothing should be written.

Both transport runtimes call this rather than deriving the document themselves, which is what makes their error bytes identical by construction instead of by two implementations agreeing.

func Redirect added in v0.5.11

func Redirect(target string, status ...int) error

Redirect returns a value that sends the browser to target. It travels the error return because a caller that returns values rather than holding a ResponseWriter has no other channel, and because a redirect and an error both end the normal response before it starts.

The value is an ordinary error: nothing panics and no control-flow exception is thrown, which is the difference from how a server-function ecosystem built on exceptions expresses this.

status defaults to 303, which is what a page wants after a POST. Pass one of 301, 302, 307, or 308 to choose another; anything else is refused here rather than emitted as a status no client will follow.

func RedirectTarget added in v0.5.11

func RedirectTarget(err error) (string, int, bool)

RedirectTarget reports the location a redirect value carries.

func ReportStreamError

func ReportStreamError(err error)

ReportStreamError hands err to the installed handler, if any.

func RestFormAny

func RestFormAny(formBody map[string]string, exclude []string) map[string]any

RestFormAny builds map[string]any from leftover form keys not in exclude.

func RestFormRaw

func RestFormRaw(formBody map[string]string, exclude []string) map[string]json.RawMessage

RestFormRaw builds map[string]json.RawMessage from leftover form keys.

func ServeSocket added in v0.5.4

func ServeSocket[In, Out any](conn MessageConn, opts SocketOptions, fn func(*Socket[In, Out]) error) error

ServeSocket applies the lifecycle to conn, runs fn, and closes the socket whatever fn returns.

Both surfaces call this, so the limits, the pong accounting, the ping cadence and the close handshake are one implementation rather than two. Closing the connection stays with the caller: fasthttp closes it when its upgrade callback returns, and net/http has to be told.

func SetMaxMultipartBodyBytes

func SetMaxMultipartBodyBytes(n int64)

SetMaxMultipartBodyBytes sets the global multipart body size limit.

n > 0  → use n bytes
n <= 0 → restore DefaultMaxMultipartBodyBytes (1 MiB)

func SetSocketDefaults added in v0.5.4

func SetSocketDefaults(opts SocketOptions)

SetSocketDefaults installs the process-wide socket options. It is shared with the fasthttp runtime, so installing them once covers both.

func SetStreamErrorHandler

func SetStreamErrorHandler(fn func(error))

SetStreamErrorHandler installs the destination for post-commit stream failures. Passing nil discards them, which is the default: a runtime logging on its own would write to a destination the caller did not choose.

func SocketHandshakeError added in v0.5.4

func SocketHandshakeError(status int, reason error) error

SocketHandshakeError shapes an upgrader's refusal as a Problem-carrying error, so a refused socket looks like every other refusal in the application rather than like the driver's own plain text. Both surfaces install it on their upgrader's Error hook, which is the only place a refusal can be reshaped: the driver writes the response itself, before any 101.

The 500 case keeps its named code for the returned error and the log only; ProblemResponse hides a 5xx code from the body, which is what should happen to a server misconfiguration.

func StatusText

func StatusText(status int) string

StatusText mirrors net/http.StatusText for the codes this package can produce and the ordinary ones beside them, and returns "" for anything else exactly as the standard library does. It exists so the error model links no transport: every constructor below sets Title, so this is reached only by a hand-built HTTPError.

func Unauthorized

func Unauthorized(problem Problem, cause ...error) error

Unauthorized returns a 401 Unauthorized error.

func ValidateSocketOptions added in v0.5.4

func ValidateSocketOptions(opts SocketOptions) error

ValidateSocketOptions rejects a combination that would serve a socket which dies on schedule.

func Validation

func Validation(fields ...FieldError) error

Validation returns a 400 Bad Request validation error with field details.

Types

type FieldError

type FieldError struct {
	Field    string
	Location string
	Message  string
}

FieldError describes a single field-level validation failure.

func Field

func Field(field, location, message string) FieldError

Field builds a field-level validation error.

type File

type File struct {
	Filename    string
	ContentType string
	Size        int64
	Content     []byte
}

File is an uploaded file bound from a multipart/form-data part. After a successful bind, Filename, ContentType (when the client sent one), Size, and Content are populated from the named file part.

func FileFromHeader

func FileFromHeader(fh *multipart.FileHeader, limit int64) (File, error)

FileFromHeader reads one multipart part into a File, bounded by limit. Both transports reach this: fasthttp's MultipartForm also yields *multipart.FileHeader, so the part-reading rule is written once.

func (File) Empty

func (f File) Empty() bool

Empty reports whether f has no filename and no content.

type HTTPError

type HTTPError struct {
	Status  int
	Title   string
	Problem Problem
	Fields  []FieldError
	// Location carries a redirect target. It is empty for every ordinary error,
	// and set only by Redirect, whose value travels the error return because a
	// redirect and an error both end the normal response before it starts.
	Location string
	// contains filtered or unexported fields
}

HTTPError is an HTTP-mapped error with optional RFC 9457 details and cause.

func AsHTTPError

func AsHTTPError(err error) (*HTTPError, bool)

AsHTTPError extracts *HTTPError from err if present. Implemented without errors.As so TinyGo does not require reflect.AssignableTo (unimplemented for interfaces in TinyGo 0.40), which otherwise panics when Bind's json.RawMessage path is also linked into the same binary.

func (*HTTPError) Error

func (e *HTTPError) Error() string

func (*HTTPError) Unwrap

func (e *HTTPError) Unwrap() error

type MessageConn added in v0.5.4

type MessageConn interface {
	ReadMessage() (messageType int, p []byte, err error)
	NextWriter(messageType int) (io.WriteCloser, error)
	WriteControl(messageType int, data []byte, deadline time.Time) error
	SetReadLimit(limit int64)
	SetReadDeadline(t time.Time) error
	SetWriteDeadline(t time.Time) error
	SetPongHandler(h func(appData string) error)
	Subprotocol() string
	Close() error
}

MessageConn is the part of a driver WebSocket connection a typed socket uses. Both driver Conn types satisfy it structurally, with no adapter.

type Problem

type Problem struct {
	Code    string
	Message string
}

Problem is an application error payload carried by status helpers.

type Socket added in v0.5.4

type Socket[In, Out any] struct {
	// contains filtered or unexported fields
}

Socket is a typed WebSocket connection carrying one JSON value per message.

Read must be called from one goroutine. Write may be called from any: it takes a lock the control frames share, so a broadcast goroutine cannot interleave its frame with a message or with a lifecycle ping.

func NewSocket added in v0.5.4

func NewSocket[In, Out any](conn MessageConn, opts SocketOptions) *Socket[In, Out]

NewSocket builds a typed socket over conn. ServeSocket is the entry that applies the lifecycle; this exists for tests and for a caller assembling one by hand.

func (*Socket[In, Out]) Close added in v0.5.4

func (s *Socket[In, Out]) Close() error

Close ends the socket with a normal-closure handshake. Idempotent.

It does not close the underlying connection: the entry that opened it does that, so the close frame is always sent before the transport goes away.

A goroutine calling this to end someone else's read loop does not interrupt a read already blocked — nothing can, under TinyGo. The reader returns when the peer answers the close frame, or when its own deadline expires.

func (*Socket[In, Out]) Read added in v0.5.4

func (s *Socket[In, Out]) Read() (In, error)

Read returns the next message decoded into In.

The read deadline is set here rather than once at setup: netdev takes a deadline by value when a read begins, so the bound has to be in place before the call rather than pushed into it afterwards.

A decode failure is returned without closing the socket, because a message this application cannot read is the application's to answer.

func (*Socket[In, Out]) Subprotocol added in v0.5.4

func (s *Socket[In, Out]) Subprotocol() string

Subprotocol returns the subprotocol the handshake negotiated, or "".

func (*Socket[In, Out]) Write added in v0.5.4

func (s *Socket[In, Out]) Write(v Out) error

Write encodes v as one text frame. Safe to call from any goroutine.

type SocketOptions added in v0.5.4

type SocketOptions struct {
	ReadLimit       int64
	IdleTimeout     time.Duration
	PingInterval    time.Duration
	WriteTimeout    time.Duration
	ReadBufferSize  int
	WriteBufferSize int

	// Subprotocols are offered in preference order; the negotiated one is
	// readable from the socket.
	Subprotocols []string

	// EnableCompression asks for permessage-deflate. It is off by default:
	// it pulls flate into the binary, and only no-context-takeover mode is
	// supported, so the saving is smaller than the cost.
	EnableCompression bool

	// CheckOrigin decides whether a handshake is allowed, from the Origin and
	// Host header values. It takes two strings rather than a request so one
	// policy serves both transports, the same reason stream negotiation does.
	// A nil value takes CheckOriginDefault.
	CheckOrigin func(origin, host string) bool
}

SocketOptions configures one socket. A zero field takes the process default installed with SetSocketDefaults, and a process default left zero takes the constant above, so nothing reaches the driver as zero.

func ResolveSocketOptions added in v0.5.4

func ResolveSocketOptions(opts SocketOptions) SocketOptions

ResolveSocketOptions fills opts from the process defaults, then from the constants. Both surfaces call it so one options value means the same thing on either.

func SocketDefaults added in v0.5.4

func SocketDefaults() SocketOptions

SocketDefaults returns the installed process defaults, with every unset field resolved to its constant.

type Stream

type Stream[T any] struct {
	// contains filtered or unexported fields
}

Stream is a typed incremental response stream over any writer.

func NewStream

func NewStream[T any](w io.Writer, format StreamFormat) *Stream[T]

NewStream builds a stream over w. The caller has already sent the headers StreamHeaders lists and the 200 status.

func (*Stream[T]) Close

func (s *Stream[T]) Close() error

Close marks the stream finished. Idempotent. For JSON array format, Close writes the trailing ']' (or "[]" if no Write). SSE and NDJSON do not require a special trailer.

func (*Stream[T]) Format

func (s *Stream[T]) Format() StreamFormat

Format returns the negotiated stream format (sse | ndjson | json-array).

func (*Stream[T]) Write

func (s *Stream[T]) Write(v T) error

Write encodes one event in the negotiated format. Callable many times; does not re-send HTTP status or headers.

Generated encoders terminate each event with a single '\n', which supplies the NDJSON line ending and the first newline of the SSE frame.

type StreamFormat

type StreamFormat string

StreamFormat is the negotiated on-the-wire format for Stream[T].

const (
	// StreamSSE is text/event-stream (data: <json>\n\n).
	StreamSSE StreamFormat = "sse"
	// StreamNDJSON is application/x-ndjson (one JSON object per line).
	// Same family as JSONL / NDJSON; not a single JSON array document.
	StreamNDJSON StreamFormat = "ndjson"
	// StreamJSONArray is application/json as one JSON array document:
	// [obj1,obj2,...] with items appended incrementally and closed by Close.
	StreamJSONArray StreamFormat = "json-array"
)

func NegotiateStream

func NegotiateStream(streamQuery, accept, userAgent string) StreamFormat

NegotiateStream selects SSE, NDJSON, or JSON array using:

  1. the ?stream= query value
  2. Accept
  3. User-Agent heuristics
  4. default NDJSON

It takes the three values as strings so each transport reads them its own way and the decision itself stays in one place.

Note: NDJSON/JSONL (line-delimited objects) is distinct from JSON array (a single [...] document). application/json selects the array form; application/x-ndjson / application/jsonl select NDJSON.

type StreamHeader

type StreamHeader struct{ Name, Value string }

StreamHeader is one response header a format requires.

func StreamHeaders

func StreamHeaders(format StreamFormat) []StreamHeader

StreamHeaders lists the headers a format's response opens with, in order, so both surfaces send the same set.

Jump to

Keyboard shortcuts

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