Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 50 additions & 14 deletions src/ClientBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const SharedCredentials = require("./SharedCredentials");
const CustomHeaderSender = require("./CustomHeaderSender");
const StatusCodeSender = require("./StatusCodeSender");
const LicenseSender = require("./LicenseSender");
const CustomQuerySender = require("@/src/CustomQuerySender");
const BadCredentialsError = require("./Errors").BadCredentialsError;
const RetrySender = require("./RetrySender");
const Sleeper = require("./util/Sleeper.ts");
Expand Down Expand Up @@ -39,7 +40,7 @@ const INTERNATIONAL_POSTAL_CODE_API_URL = "https://international-postal-code.api
*/
class ClientBuilder {
constructor(signer) {
if (noCredentialsProvided()) throw new BadCredentialsError();
if (!credentialsProvided()) throw new BadCredentialsError();

this.signer = signer;
this.httpSender = undefined;
Expand All @@ -50,15 +51,16 @@ class ClientBuilder {
this.customHeaders = {};
this.debug = undefined;
this.licenses = [];
this.customQueries = new Map();

function noCredentialsProvided() {
return (!signer) instanceof StaticCredentials || (!signer) instanceof SharedCredentials;
function credentialsProvided() {
return signer instanceof StaticCredentials || signer instanceof SharedCredentials;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this because it was working as a false positive. I also updated it to be the positive condition for readability

}
}

/**
* @param retries The maximum number of times to retry sending the request to the API. (Default is 5)
* @return Returns <b>this</b> to accommodate method chaining.
* @return ClientBuilder <b>this</b> to accommodate method chaining.
*/
withMaxRetries(retries) {
this.maxRetries = retries;
Expand All @@ -68,7 +70,7 @@ class ClientBuilder {
/**
* @param timeout The maximum time (in milliseconds) to wait for a connection, and also to wait for <br>
* the response to be read. (Default is 10000)
* @return Returns <b>this</b> to accommodate method chaining.
* @return ClientBuilder <b>this</b> to accommodate method chaining.
*/
withMaxTimeout(timeout) {
this.maxTimeout = timeout;
Expand All @@ -77,7 +79,7 @@ class ClientBuilder {

/**
* @param sender Default is a series of nested senders. See <b>buildSender()</b>.
* @return Returns <b>this</b> to accommodate method chaining.
* @return ClientBuilder <b>this</b> to accommodate method chaining.
*/
withSender(sender) {
this.httpSender = sender;
Expand All @@ -87,7 +89,7 @@ class ClientBuilder {
/**
* This may be useful when using a local installation of the Smarty APIs.
* @param url Defaults to the URL for the API corresponding to the <b>Client</b> object being built.
* @return Returns <b>this</b> to accommodate method chaining.
* @return ClientBuilder <b>this</b> to accommodate method chaining.
*/
withBaseUrl(url) {
this.baseUrl = url;
Expand All @@ -101,7 +103,7 @@ class ClientBuilder {
* @param protocol The protocol on the proxy server to which you wish to connect. If the proxy server uses HTTPS, then you must set the protocol to 'https'.
* @param username The username to login to the proxy.
* @param password The password to login to the proxy.
* @return Returns <b>this</b> to accommodate method chaining.
* @return ClientBuilder <b>this</b> to accommodate method chaining.
*/
withProxy(host, port, protocol, username, password) {
this.proxy = {
Expand All @@ -123,35 +125,68 @@ class ClientBuilder {
/**
* Use this to add any additional headers you need.
* @param customHeaders A String to Object <b>Map</b> of header name/value pairs.
* @return Returns <b>this</b> to accommodate method chaining.
* @return ClientBuilder <b>this</b> to accommodate method chaining.
*/
withCustomHeaders(customHeaders) {
this.customHeaders = customHeaders;

return this;
}

/**
* Enables debug mode, which will print information about the HTTP request and response to console.log
* @return Returns <b>this</b> to accommodate method chaining.
* @return ClientBuilder <b>this</b> to accommodate method chaining.
*/
withDebug() {
this.debug = true;

return this;
}

/**
* Allows the caller to specify the subscription license (aka "track") they wish to use.
* @param licenses A String Array of licenses.
* @returns Returns <b>this</b> to accommodate method chaining.
* @returns ClientBuilder <b>this</b> to accommodate method chaining.
*/
withLicenses(licenses) {
this.licenses = licenses;
return this;
}

/**
* Allows the caller to specify key and value pair that is added to the request
* @param {string} key - The query parameter key
* @param {string} value - The query parameter value
* @return ClientBuilder <b>this</b> to accommodate method chaining.
*/
withCustomQuery(key, value) {
this.customQueries.set(key, value);
return this;
}

/**
* Allows the caller to specify key and value pair and appends the value associated with the key, seperated by a comma.
* @param {string} key - The query parameter key
* @param {string} value - The query parameter value
* @return ClientBuilder <b>this</b> to accommodate method chaining.
*/
withCustomCommaSeperatedQuery(key, value) {
let values = this.customQueries.get(key);
if (values === "") {
values = value;
} else {
values += "," + value;
}
this.customQueries.set(key, values);
return this;
}

/**
* Adds to the request query to use the component analysis feature.
* @return ClientBuilder <b>this</b> to accommodate method chaining.
*/
withFeatureComponentAnalysis() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the error I get for this:

node examples/us_street.mjs
file:///Users/mitchell/Smarty/smartystreets-javascript-sdk/examples/us_street.mjs:21
let client = clientBuilder.buildUsStreetApiClient();
^

TypeError: Cannot read properties of undefined (reading 'buildUsStreetApiClient')
at file:///Users/mitchell/Smarty/smartystreets-javascript-sdk/examples/us_street.mjs:21:28
at ModuleJob.run (node:internal/modules/esm/module_job:234:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:473:24)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:122:5)

Node.js v20.18.1

return this.withCustomCommaSeperatedQuery("features", "component-analysis");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same error with this:
`node examples/us_street.mjs
file:///Users/mitchell/Smarty/smartystreets-javascript-sdk/examples/us_street.mjs:21
let client = clientBuilder.buildUsStreetApiClient();
^

TypeError: Cannot read properties of undefined (reading 'buildUsStreetApiClient')
at file:///Users/mitchell/Smarty/smartystreets-javascript-sdk/examples/us_street.mjs:21:28
at ModuleJob.run (node:internal/modules/esm/module_job:234:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:473:24)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:122:5)

Node.js v20.18.1`

}

buildSender() {
if (this.httpSender) return this.httpSender;

Expand All @@ -166,8 +201,9 @@ class ClientBuilder {
const customHeaderSender = new CustomHeaderSender(agentSender, this.customHeaders);
const baseUrlSender = new BaseUrlSender(customHeaderSender, this.baseUrl);
const licenseSender = new LicenseSender(baseUrlSender, this.licenses);
const customQuerySender = new CustomQuerySender(licenseSender, this.customQueries);

return licenseSender;
return customQuerySender;
}

buildClient(baseUrl, Client) {
Expand Down
20 changes: 20 additions & 0 deletions src/CustomQuerySender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Request, Response, Sender } from "./types";

export default class CustomQuerySender {
private queries: Map<string, string>;
private sender: Sender;

constructor(innerSender: Sender, queries: Map<string, string>) {
this.queries = queries;
this.sender = innerSender;
}

send(request: Request): Promise<Response> {
this.queries.forEach((value, key) => {
const existingValue = request.parameters[key];
request.parameters[key] = existingValue ? `${existingValue},${value}` : value;
});

return this.sender.send(request);
}
}
33 changes: 33 additions & 0 deletions tests/test_CustomQuerySender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { expect } from "chai";
import CustomQuerySender from "../src/CustomQuerySender";
import Request from "../src/Request.js";
import Response from "../src/Response.js";
import { Sender } from "@/src/types";

describe("A custom query sender", function () {
it("adds custom query parameters to the request.", function () {
class MockSender implements Sender {
request?: Request;

send = (request: Request): Promise<Response> => {
this.request = request;
return Promise.resolve(new Response(200, {}));
};
}

const mockSender = new MockSender();
const customQueries = new Map<string, string>([
["a", "1"],
["b", "2"],
]);
const customQuerySender = new CustomQuerySender(mockSender, customQueries);
const request = new Request();

customQuerySender.send(request);

expect("a" in mockSender.request!.parameters).to.equal(true);
expect((mockSender.request!.parameters as Record<string, string>)["a"]).to.equal("1");
expect("b" in mockSender.request!.parameters).to.equal(true);
expect((mockSender.request!.parameters as Record<string, string>)["b"]).to.equal("2");
});
});