Skip to content

Commit

Permalink
[FEAT] added ability for client to set a status on a header (#386)
Browse files Browse the repository at this point in the history
  • Loading branch information
aricart committed Oct 18, 2022
1 parent 80aa72c commit c9705d5
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 30 deletions.
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);
});

0 comments on commit c9705d5

Please sign in to comment.