@cerbos/http
Version:
Client library for interacting with the Cerbos policy decision point service over HTTP from browser-based applications
115 lines • 3.9 kB
JavaScript
import { fromJson as bufFromJson } from "@bufbuild/protobuf";
import { NotOK, Status } from "@cerbos/core";
import { AbstractErrorResponse, cancelBody, isObject, methodName, } from "@cerbos/core/~internal";
import { endpoints } from "./endpoints.js";
export class Transport {
baseUrl;
userAgent;
constructor(baseUrl, userAgent) {
this.baseUrl = baseUrl;
this.userAgent = userAgent;
}
async unary(method, request, headers, abortHandler) {
const response = await this.fetch(method, request, headers, abortHandler);
const json = (await response.json());
if (!response.ok) {
throw new ErrorResponse(json);
}
return fromJson(method.output, json);
}
async *serverStream(method, request, headers, abortHandler) {
const response = await this.fetch(method, request, headers, abortHandler);
if (!response.body) {
throw new Error("Missing response body");
}
try {
for await (const line of eachLine(response.body)) {
const message = JSON.parse(line);
if (!isObject(message)) {
throw new Error(`Unexpected message: wanted object, got ${line}`);
}
const { result, error } = message;
if (error) {
throw new ErrorResponse(error);
}
if (!result) {
throw new Error(`Missing result in ${line}`);
}
yield fromJson(method.output, result);
}
}
finally {
cancelBody(response);
}
}
async fetch(method, request, headers, abortHandler) {
const endpoint = endpoints.get(methodName(method));
if (!endpoint) {
throw new NotOK(Status.UNIMPLEMENTED, "Unimplemented");
}
headers.set("User-Agent", this.userAgent);
const init = {
url: this.baseUrl + endpoint.path,
method: endpoint.method,
headers,
};
if (abortHandler.signal) {
init.signal = abortHandler.signal;
}
const { url, ...rest } = endpoint.serialize(request, init);
return await fetch(url, rest);
}
}
export async function* eachLine(stream) {
const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
let buffer = "";
let start = 0;
for await (const chunk of stream) {
buffer += utf8Decoder.decode(chunk, { stream: true });
let end;
while ((end = buffer.indexOf("\n", start)) >= 0) {
yield buffer.slice(start, end);
start = end + 1;
}
buffer = buffer.slice(start);
start = 0;
}
if (buffer.length > 0) {
yield buffer;
}
}
class ErrorResponse extends AbstractErrorResponse {
jsonDetails;
constructor(json) {
if (!isStatus(json)) {
throw new Error(`Invalid error response: ${JSON.stringify(json)}`);
}
super(json.code, json.message);
this.jsonDetails = json.details;
}
*details() {
if (!this.jsonDetails) {
return;
}
for (const details of this.jsonDetails) {
if (isAny(details)) {
const { "@type": typeUrl, ...value } = details;
yield { typeUrl, value };
}
}
}
parseDetails = fromJson;
}
function isStatus(json) {
return (isObject(json) &&
typeof json["code"] === "number" &&
typeof json["message"] === "string" &&
(json["details"] === undefined || Array.isArray(json["details"])));
}
function isAny(json) {
return isObject(json) && typeof json["@type"] === "string";
}
function fromJson(schema, json) {
return bufFromJson(schema, json, { ignoreUnknownFields: true });
}
//# sourceMappingURL=transport.js.map