Skip to content

0.4.0

Choose a tag to compare

@zaiev zaiev released this 13 Oct 21:27
· 68 commits to main since this release

Spindle moves to wiretype 0.4.0 and rowtype 0.6.0, types statuses everywhere, holds route tables and cookies to stricter rules, and fixes WebSocket, client and server edge cases.

Upgrading

Dependencies

  • spindle needs wiretype 0.4.0; spindle_postgres needs rowtype 0.6.0 and postgres-eio 0.4.0.
  • A DEFERRABLE constraint that refuses COMMIT is `Conflict with its name, was `Not_committed and a 500.
  • Rowtype_postgres.Conninfo.t is private: use Conninfo.with_database and Conninfo.with_connect_timeout.
  • OpenAPI and Zod write a parameterised type where it is used, not as a shared component, unless [@@kind] names it.

Route tables

  • App.make refuses a JSON answer under 204 or 205: use Returns.empty or Returns.empty_response.
  • App.make refuses Returns.json ~status or Returns.empty ~status outside 2xx; a redirect is Returns.response with Response.redirect.
  • App.make refuses a Refusal.Code whose status is not 4xx or 5xx.

Statuses

  • Spindle_client's status is a Spindle_http.Status.t, was an int: match `OK or use Status.to_int.
  • Spindle.Test.response's status is a Spindle_http.Status.t too: match `OK or compare Status.to_int r.status.
  • Head.Response.read answers Head.Response.error (Closed, Malformed of string), was `Closed / `Malformed.
  • A Structured Field date is Structured.Date of Ptime.t, was an int of seconds; years outside 0–9999 are refused.

Client errors

  • Spindle_client.error says whether the request went out: Unreachable → Unsent (never left) or Unanswered (sent, not answered whole).
  • Treat Timeout as Unanswered; a client span's failure says unsent or unanswered.

Cookies, keys and forms

  • Cookie.signed / Cookie.encrypted: ~max_age_s:86400 → ~max_age:Mtime.Span.(1 * day), a span. Cookie.make ?max_age_s stays seconds.
  • Key.sign and Key.seal use their own derived keys: tokens made before no longer verify or open. Cookies are unaffected.
  • Form.file, Form.file_opt, Form.files → Form.File.required, Form.File.optional, Form.File.list, each a Form.File.t.

CORS

  • Under Cors.Any, a cross-site write carrying Authorization: Basic, Digest, Negotiate or NTLM is 403 cross_origin, as a Cookie is. Bearer works as before.

New

  • Spindle_client connects through the net it is given, so a test can pass Eio_mock.Net.
  • A raising WebSocket handler is logged once at error, with what was raised and where; a client's line says where it went and how long it lived.

Fixed

  • A peer reading a large WebSocket message slowly is no longer cut off: send_timeout runs from the last byte it took.
  • A WebSocket message's buffer grows as bytes arrive; fourteen bytes could hold max_message of memory.
  • Spindle_client sends a URL's path and query as written: %3B, %2F and + are kept, so signed URLs stay valid.
  • A JSON map key read in more than one spelling is refused when given twice.
  • Kinds that refer to each other in a ring of three or more are written as references, not as any JSON.
  • Rate.create raises Invalid_argument for a per shorter than limit nanoseconds, which limited nothing.
  • Refusal.busy writes a Retry-After of at least one second.
  • Expect: 100-continue among other elements, or in a second field, gets 100 Continue.
  • A failed accept (out of descriptors, reset) is logged and retried after 10 ms, where it stopped the server.
  • Response.stream sends from several fibers one after another, and send_timeout ends each.
  • Path.url writes a . or .. segment as %2E or %2E%2E.
  • A client connection that fails to connect leaves no socket behind.