75 lines
3.3 KiB
Markdown
75 lines
3.3 KiB
Markdown
# 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"`
|
|
|
|
## 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](../surf/README.md) uses it for the opaque 500 response of its panic recovery, and application handlers use it directly for their JSON responses.
|
|
|
|
## Features
|
|
|
|
- `wire.WriteJSON` encodes a value with HTML escaping disabled and without the trailing newline `encoding/json` adds, then sets `Content-Type: application/json` and the status code. If encoding fails, it writes the opaque 500 body instead of a partial response.
|
|
- `wire.WriteOpaque500` writes a 500 response with the fixed body `{"error":true,"message":"Internal server error"}`, which reveals nothing about the failure.
|
|
- `wire.Time` wraps `time.Time` and always marshals in UTC as `2006-01-02T15:04:05+00:00` (Carbon's form, never Go's `Z`). It unmarshals a timestamp with a numeric offset or an RFC 3339 `Z` timestamp, and turns `null` into the zero time.
|
|
- `wire.TriBool` models a nullable boolean: when `wire.TriBool.Valid` is false it marshals as `null`, otherwise as `wire.TriBool.Value`.
|
|
- `wire.Slice` returns a non-nil empty slice for a nil input, so optional lists marshal as `[]` instead of `null`.
|
|
|
|
## Usage
|
|
|
|
```go
|
|
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
|
|
|
|
```sh
|
|
go test ./modules/wire/...
|
|
```
|
|
|
|
The tests use `net/http/httptest` and need no external services.
|