Each module README now says where the module sits in the dependency graph, why it is its own module, and lists use cases for application developers.
wire
JSON response helpers and value types that keep API bodies byte-compatible with a PHP (WinterCMS/Laravel) backend.
import "git.golem15.com/golem15/summercms/modules/wire"
Name: What goes over the wire; it keeps response bodies byte-for-byte the same as the PHP backend's.
Overview
wire is the lowest layer of the HTTP stack: it decides how a Go value becomes response bytes. Its helpers reproduce what json_encode and Carbon produce in a WinterCMS or Laravel app (unescaped HTML characters, no trailing newline, +00:00 timestamps, nullable booleans, [] rather than null for empty lists), so an endpoint ported from PHP returns the same body its existing clients already parse. surf uses it for the opaque 500 response of its panic recovery, and application handlers use it directly for their JSON responses.
Role in the framework
A dependency-free leaf for response shapes. surf and lighthouse use it, and so do plugin controllers. It is separate so every handler produces the same JSON as the PHP backend being replaced, byte for byte.
Use cases:
- Return paginated lists and error envelopes in the shape an existing frontend expects.
- Encode timestamps, nullable booleans and empty lists the way Laravel serializes them.
- Port endpoints one at a time without the frontend noticing the backend changed.
Features
wire.WriteJSONencodes a value with HTML escaping disabled and without the trailing newlineencoding/jsonadds, then setsContent-Type: application/jsonand the status code. If encoding fails, it writes the opaque 500 body instead of a partial response.wire.WriteOpaque500writes a 500 response with the fixed body{"error":true,"message":"Internal server error"}, which reveals nothing about the failure.wire.Timewrapstime.Timeand always marshals in UTC as2006-01-02T15:04:05+00:00(Carbon's form, never Go'sZ). It unmarshals a timestamp with a numeric offset or an RFC 3339Ztimestamp, and turnsnullinto the zero time.wire.TriBoolmodels a nullable boolean: whenwire.TriBool.Validis false it marshals asnull, otherwise aswire.TriBool.Value.wire.Slicereturns a non-nil empty slice for a nil input, so optional lists marshal as[]instead ofnull.
Usage
package blog
import (
"net/http"
"time"
"git.golem15.com/golem15/summercms/modules/wire"
)
type postJSON struct {
ID uint `json:"id"`
Title string `json:"title"`
Tags []string `json:"tags"`
Featured wire.TriBool `json:"featured"`
PublishedAt wire.Time `json:"published_at"`
}
func showPost(w http.ResponseWriter, r *http.Request) {
var tags []string // nil when the post has no tags
body := postJSON{
ID: 1,
Title: "Hello & welcome", // "&" stays unescaped
Tags: wire.Slice(tags), // marshals as []
Featured: wire.TriBool{}, // marshals as null
PublishedAt: wire.Time{Time: time.Now()}, // "...+00:00"
}
wire.WriteJSON(w, http.StatusOK, map[string]any{"data": body})
}
API reference
| Identifier | Description |
|---|---|
wire.WriteJSON |
Writes a JSON body with HTML escaping off and no trailing newline; falls back to the opaque 500 on an encoding error. |
wire.WriteOpaque500 |
Writes the fixed {"error":true,"message":"Internal server error"} 500 response. |
wire.Time |
time.Time wrapper that marshals as UTC +00:00 and reads both +00:00 and Z forms. |
wire.TriBool |
Nullable boolean: wire.TriBool.Valid false marshals null, otherwise wire.TriBool.Value. |
wire.Slice |
Generic helper that turns a nil slice into an empty one so it marshals as []. |
Dependencies
- SummerCMS modules: none.
- Third-party: none.
- Standard library:
bytes,encoding/json,net/http,time.
Testing
go test ./modules/wire/...
The tests use net/http/httptest and need no external services.