docs: stdlib envelope/replace notes + dev-insecure-key (S2, S3, O1)

- S2: document that the response envelope is statusCode-gated — a returned
  map is only unwrapped into {statusCode, headers, body} when it contains
  statusCode, else the whole map silently becomes the 200 body.
- S3: note that Rhai String.replace() mutates in place and returns (),
  with the sub_string bearer-parsing idiom.
- Document ctx.request fields including the new `method`.
- O1: document PICLOUD_DEV_INSECURE_KEY next to PICLOUD_DEV_MODE in
  CLAUDE.md (the acknowledgement var was previously only in the startup
  error).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-12 20:43:26 +02:00
parent 7ffbb8de87
commit b83113846f
2 changed files with 55 additions and 0 deletions

View File

@@ -37,6 +37,17 @@ These come with the Rhai engine itself. See the
`index_of`, `split`, `trim`, `to_lower`, `to_upper`, `replace`, `chars`,
`pad`, `sub_string`, `crop`, `+` (concatenation).
> **Footgun — `replace` mutates in place and returns `()`.** Rhai's
> `String.replace(from, to)` edits the receiver and returns unit, *not*
> a new string. `let t = auth.replace("Bearer ", "")` sets `t` to `()`,
> so a downstream `users::verify(t)` fails with
> `Function not found: users::verify (())`. To strip a known prefix,
> slice instead:
> ```rhai
> let token = if auth.starts_with("Bearer ") { auth.sub_string(7) } else { auth };
> ```
> Or call `replace` purely for its side effect: `let t = auth; t.replace("Bearer ", "");`.
**Array:** `push`, `pop`, `shift`, `insert`, `remove`, `len`, `clear`,
`truncate`, `extend`, `filter`, `map`, `reduce`, `reduce_rev`, `find`,
`find_map`, `any`, `all`, `index_of`, `contains`, `sort`, `reverse`,
@@ -203,6 +214,47 @@ let qs = url::encode_query(#{ q: "rust regex", page: 2 });
---
## Request context (`ctx`) and the response envelope
Every script sees a `ctx` map describing the invocation. The request
fields under `ctx.request` are:
| Field | Type | Notes |
|---|---|---|
| `ctx.request.path` | `String` | Request path. |
| `ctx.request.method` | `String` | Uppercased HTTP verb (`"GET"`, `"POST"`, …). Empty for non-HTTP triggers. Branch on it to serve several verbs from one script. |
| `ctx.request.headers` | `Map` | Lowercased header names → values. |
| `ctx.request.body` | parsed | Already-parsed JSON when `Content-Type: application/json`; otherwise a string. |
| `ctx.request.params` | `Map` | Captures from `:name` route segments. |
| `ctx.request.query` | `Map` | Query-string parameters. |
| `ctx.request.rest` | `String` | Suffix captured by a `prefix` (`/*`) route. |
```rhai
// One script, many verbs:
let m = ctx.request.method;
if m == "GET" { /* list */ }
else if m == "POST" { /* create */ }
else { return #{ statusCode: 405, body: #{ error: "method not allowed" } }; }
```
### The response envelope is `statusCode`-gated
A returned map is unwrapped into `{statusCode, headers, body}` **only when
it contains a `statusCode` key**. Without `statusCode`, the *entire*
returned map becomes the response body, with an implicit `200`:
```rhai
// WRONG — no statusCode, so the whole map is the body:
#{ body: #{ token: t } } // → HTTP 200 {"body":{"token":"…"}} (double-nested!)
// RIGHT — statusCode present, so it's treated as the envelope:
#{ statusCode: 200, body: #{ token: t } } // → HTTP 200 {"token":"…"}
```
Always include `statusCode` when you mean to set headers or a specific
body shape. Returning a bare value (`42`, `#{ ok: true }`) is fine — it
becomes a `200` body as-is.
## What's not here
- **Crypto** (sha256/hmac/argon2/encryption) — deferred to a focused