document(['data' => $data], 200); } public function created(array $data = [], ?string $location = null): ResponseInterface { $res = $this->document(['data' => $data], 201); if ($location) { $res = $res->withHeader('Location', $location); } return $res; } public function noContent(): ResponseInterface { // JSON:API allows 204 without body return $this->psr17->createResponse(204); } public function error(int $status, string $title, ?string $detail = null, array $extra = []): ResponseInterface { $error = array_filter([ 'status' => (string) $status, 'title' => $title, 'detail' => $detail, ], static fn($v) => $v !== null && $v !== ''); if (!empty($extra)) { $error = array_merge($error, $extra); } return $this->document(['errors' => [$error]], $status); } public function fromArray(array $payload, int $status = 200): ResponseInterface { // Caller must ensure payload is a valid JSON:API document shape return $this->document($payload, $status); } /** * @param array $doc */ private function document(array $doc, int $status): ResponseInterface { $res = $this->psr17->createResponse($status) ->withHeader('Content-Type', 'application/vnd.api+json'); $res->getBody()->write(json_encode($doc, JSON_UNESCAPED_SLASHES)); return $res; } }