|null the body actually applied, if any */ public function applyRequestOverrides(WP_REST_Request $request, Verdict $verdict): ?array { $body = $verdict->body(); if ($body !== null) { // Set the raw body first: WP_REST_Request::set_body() resets the // parsed-JSON flag, so a later get_json_params() re-parses the // replacement rather than serving the original from cache. if (is_array($request->get_json_params())) { $request->set_body((string) wp_json_encode($body)); } $request->set_body_params($body); } Context::set($verdict, $body); return $body; } /** Apply a verdict's response headers and declarative field transforms. */ public function applyResponse(WP_REST_Response $response, Verdict $verdict): WP_REST_Response { foreach ($verdict->responseHeaders() as $header) { if (isset($header['name'], $header['value'])) { $response->header((string) $header['name'], (string) $header['value']); } } $ops = $verdict->responseTransform(); $data = $response->get_data(); if ($ops !== [] && is_array($data)) { $response->set_data(Transform::apply($data, $ops)); } return $response; } /** * @param mixed $result the pre-computed dispatch result (non-null = already short-circuited) * @return mixed */ public function preDispatch(mixed $result, WP_REST_Server $server, WP_REST_Request $request): mixed { if ($result !== null) { return $result; // someone else already answered; leave it alone } $method = strtoupper((string) $request->get_method()); $path = Http::path(); if (!$this->shim->shouldEvaluate($method, $path)) { return $result; } $verdict = $this->shim->evaluate($method, $path, [ 'body' => $this->body($request), 'headers' => $this->headers($request), 'preview' => (string) ($request->get_header('x-sarcio-preview') ?? ''), ]); if ($verdict === null || $verdict->isProceed()) { return $result; // fail closed / nothing to do } if ($verdict->isRespond()) { return self::staticResponse($verdict); } $this->applyRequestOverrides($request, $verdict); $this->verdict = $verdict; do_action('sarcio_verdict_applied', $verdict, $request); return $result; } /** * @param mixed $result * @return mixed */ public function postDispatch(mixed $result, WP_REST_Server $server, WP_REST_Request $request): mixed { if ($this->verdict === null || !$result instanceof WP_REST_Response) { return $result; } return $this->applyResponse($result, $this->verdict); } /** Build the short-circuit response a `respond` verdict describes. */ public static function staticResponse(Verdict $verdict): WP_REST_Response { $headers = []; foreach ($verdict->responseHeaderList() as $header) { if (isset($header['name'], $header['value'])) { $headers[(string) $header['name']] = (string) $header['value']; } } return new WP_REST_Response($verdict->responseBody(), $verdict->responseStatus(), $headers); } /** @return array */ private function body(WP_REST_Request $request): array { $json = $request->get_json_params(); if (is_array($json)) { return $json; } $params = $request->get_body_params(); return is_array($params) ? $params : []; } /** * WP_REST_Request normalizes header names to `content_type` style and keeps * every value as a list; the shim's allowlist is keyed on the wire names, so * translate back before handing them over. * * @return array */ private function headers(WP_REST_Request $request): array { $out = []; foreach ($request->get_headers() as $name => $values) { $wire = strtolower(str_replace('_', '-', (string) $name)); $out[$wire] = is_array($values) ? implode(', ', array_map('strval', $values)) : (string) $values; } return $out; } }