Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] added ability for client to set a status on a header #386

Merged
merged 1 commit into from
Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 29 additions & 15 deletions nats-base-client/headers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 The NATS Authors
* Copyright 2020-2022 The NATS Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
Expand Down Expand Up @@ -77,8 +77,11 @@ export function canonicalMIMEHeaderKey(k: string): string {
return String.fromCharCode(...buf);
}

export function headers(): MsgHdrs {
return new MsgHdrsImpl();
export function headers(code = 0, description = ""): MsgHdrs {
if ((code === 0 && description !== "") || (code > 0 && description === "")) {
throw new Error("setting status requires both code and description");
}
return new MsgHdrsImpl(code, description);
}

const HEADER = "NATS/1.0";
Expand All @@ -93,14 +96,14 @@ export enum Match {
}

export class MsgHdrsImpl implements MsgHdrs {
code: number;
_code: number;
headers: Map<string, string[]>;
description: string;
_description: string;

constructor() {
this.code = 0;
constructor(code = 0, description = "") {
this._code = code;
this._description = description;
this.headers = new Map();
this.description = "";
}

[Symbol.iterator]() {
Expand All @@ -114,7 +117,7 @@ export class MsgHdrsImpl implements MsgHdrs {
equals(mh: MsgHdrsImpl): boolean {
if (
mh && this.headers.size === mh.headers.size &&
this.code === mh.code
this._code === mh._code
) {
for (const [k, v] of this.headers) {
const a = mh.values(k);
Expand All @@ -141,10 +144,10 @@ export class MsgHdrsImpl implements MsgHdrs {
const h = lines[0];
if (h !== HEADER) {
let str = h.replace(HEADER, "");
mh.code = parseInt(str, 10);
const scode = mh.code.toString();
mh._code = parseInt(str, 10);
const scode = mh._code.toString();
str = str.replace(scode, "");
mh.description = str.trim();
mh._description = str.trim();
}
if (lines.length >= 1) {
lines.slice(1).map((s) => {
Expand All @@ -162,10 +165,13 @@ export class MsgHdrsImpl implements MsgHdrs {
}

toString(): string {
if (this.headers.size === 0) {
if (this.headers.size === 0 && this._code === 0) {
return "";
}
let s = HEADER;
if (this._code > 0 && this._description !== "") {
s += ` ${this._code} ${this._description}`;
}
for (const [k, v] of this.headers) {
for (let i = 0; i < v.length; i++) {
s = `${s}\r\n${k}: ${v[i]}`;
Expand Down Expand Up @@ -278,11 +284,11 @@ export class MsgHdrsImpl implements MsgHdrs {
}

get hasError() {
return this.code >= 300;
return this._code >= 300;
}

get status(): string {
return `${this.code} ${this.description}`.trim();
return `${this._code} ${this._description}`.trim();
}

toRecord(): Record<string, string[]> {
Expand All @@ -293,6 +299,14 @@ export class MsgHdrsImpl implements MsgHdrs {
return data;
}

get code(): number {
return this._code;
}

get description(): string {
return this._description;
}

static fromRecord(r: Record<string, string[]>): MsgHdrs {
const h = new MsgHdrsImpl();
for (const k in r) {
Expand Down
6 changes: 2 additions & 4 deletions nats-base-client/jsutil.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 The NATS Authors
* Copyright 2021-2022 The NATS Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
Expand Down Expand Up @@ -116,9 +116,7 @@ export function newJsErrorMsg(
description: string,
subject: string,
): Msg {
const h = headers() as MsgHdrsImpl;
h.code = code;
h.description = description;
const h = headers(code, description) as MsgHdrsImpl;

const arg = { hdr: 1, sid: 0, size: 0 } as MsgArg;
const msg = new MsgImpl(arg, Empty, {} as Publisher);
Expand Down
16 changes: 6 additions & 10 deletions nats-base-client/msg.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 The NATS Authors
* Copyright 2020-2022 The NATS Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
Expand All @@ -20,15 +20,11 @@ import { TD } from "./encoders.ts";
import { ErrorCode, NatsError } from "./error.ts";

export function isRequestError(msg: Msg): NatsError | null {
// to consider an error from the server we expect no payload
if (msg && msg.data.length === 0 && msg.headers) {
const headers = msg.headers as MsgHdrsImpl;
if (headers.hasError) {
// only 503s are expected from core NATS (404/408/409s are JetStream)
if (headers.code === 503) {
return NatsError.errorForCode(ErrorCode.NoResponders);
}
}
// NATS core only considers errors 503s on messages that have no payload
// everything else simply forwarded as part of the message and is considered
// application level information
if (msg && msg.data.length === 0 && msg.headers?.code === 503) {
return NatsError.errorForCode(ErrorCode.NoResponders);
}
return null;
}
Expand Down
39 changes: 38 additions & 1 deletion tests/headers_test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 The NATS Authors
* Copyright 2020-2022 The NATS Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
Expand Down Expand Up @@ -28,6 +28,7 @@ import { NatsServer } from "./helpers/launcher.ts";
import {
assert,
assertEquals,
assertRejects,
assertThrows,
} from "https://deno.land/std@0.152.0/testing/asserts.ts";
import {
Expand All @@ -37,6 +38,7 @@ import {
} from "../nats-base-client/internal_mod.ts";
import { Publisher } from "../nats-base-client/protocol.ts";
import { TestDispatcher } from "./parser_test.ts";
import { cleanup, setup } from "./jstest_util.ts";

Deno.test("headers - illegal key", () => {
const h = headers();
Expand Down Expand Up @@ -321,3 +323,38 @@ Deno.test("headers - error headers may have other entries", () => {
assertEquals(m.headers.get(JsHeaders.LastConsumerSeqHdr), "1");
assertEquals(m.headers.get(JsHeaders.LastStreamSeqHdr), "1");
});

Deno.test("headers - code/description", () => {
assertThrows(
() => {
headers(500);
},
Error,
"setting status requires both code and description",
);

assertThrows(
() => {
headers(0, "some message");
},
Error,
"setting status requires both code and description",
);
});

Deno.test("headers - codec", async () => {
const { ns, nc } = await setup({}, {});

nc.subscribe("foo", {
callback: (err, msg) => {
const h = headers(500, "custom status from client");
msg.respond(Empty, { headers: h });
},
});

const r = await nc.request("foo", Empty);
assertEquals(r.headers?.code, 500);
assertEquals(r.headers?.description, "custom status from client");

await cleanup(ns, nc);
});