diff --git a/examples/international_postal_code.mjs b/examples/international_postal_code.mjs new file mode 100644 index 0000000..f78f9c2 --- /dev/null +++ b/examples/international_postal_code.mjs @@ -0,0 +1,70 @@ +import SmartySDK from "smartystreets-javascript-sdk"; + +const SmartyCore = SmartySDK.core; +const Lookup = SmartySDK.internationalPostalCode.Lookup; + +// for Server-to-server requests, use this code: +// let authId = process.env.SMARTY_AUTH_ID; +// let authToken = process.env.SMARTY_AUTH_TOKEN; +// const credentials = new SmartyCore.StaticCredentials(authId, authToken); + +// for client-side requests (browser/mobile), use this code: +let key = process.env.SMARTY_EMBEDDED_KEY; +const credentials = new SmartyCore.SharedCredentials(key); + +// The appropriate license values to be used for your subscriptions +// can be found on the Subscription page of the account dashboard. +// https://www.smarty.com/docs/cloud/licensing +let clientBuilder = new SmartyCore.ClientBuilder(credentials); +// .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API +let client = clientBuilder.buildInternationalPostalCodeClient(); + +// Documentation for input fields can be found at: +// https://www.smarty.com/docs/cloud/international-postal-code-api#input-fields + +// Lookup by postal code and country +let lookup1 = new Lookup("Australia", "2776"); +// uncomment the following line to add a custom parameter +// lookup1.addCustomParameter("input_id", 1234); + +// Lookup by locality, administrative area, and country +let lookup2 = new Lookup("Brazil", null, "SP", "Sao Paulo", "ID-8675309"); + +await handleResponse(lookup1, "Postal code lookup"); +await handleResponse(lookup2, "Locality and administrative area lookup"); + +function displayResult(lookup, message) { + console.log("*** " + message + " ***"); + if (lookup.result && lookup.result.length > 0) { + lookup.result.forEach((result) => { + console.log("Input ID:", result.inputId); + console.log("Administrative Area:", result.administrativeArea); + console.log("Super Administrative Area:", result.superAdministrativeArea); + console.log("Sub Administrative Area:", result.subAdministrativeArea); + console.log("Locality:", result.locality); + console.log("Dependent Locality:", result.dependentLocality); + console.log("Dependent Locality Name:", result.dependentLocalityName); + console.log("Double Dependent Locality:", result.doubleDependentLocality); + console.log("Postal Code:", result.postalCode); + console.log("Postal Code Extra:", result.postalCodeExtra); + console.log("Country ISO3:", result.countryIso3); + console.log("---"); + }); + } else { + console.log("No results found"); + } + console.log("\n"); +} + +function handleError(error) { + console.log("ERROR:", error); +} + +async function handleResponse(lookup, lookupType) { + try { + const result = await client.send(lookup); + displayResult(result, lookupType); + } catch (err) { + handleError(err); + } +} diff --git a/examples/us_zipcode.mjs b/examples/us_zipcode.mjs index 0c521c6..b34633c 100644 --- a/examples/us_zipcode.mjs +++ b/examples/us_zipcode.mjs @@ -13,7 +13,7 @@ let key = process.env.SMARTY_EMBEDDED_KEY; const credentials = new SmartyCore.SharedCredentials(key); let clientBuilder = new SmartyCore.ClientBuilder(credentials); - // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API +// .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API let client = clientBuilder.buildUsZipcodeClient(); @@ -45,17 +45,19 @@ batch.add(lookup3); await handleResponse(batch); function viewResults(response) { - response.lookups.map(lookup => lookup.result.map(candidate => { - candidate.cities.map(city => console.log(city.city)); - // candidate.zipcodes.map(zipcode => console.log(zipcode.zipcode)); - })); + response.lookups.map((lookup) => + lookup.result.map((candidate) => { + candidate.cities.map((city) => console.log(city.city)); + // candidate.zipcodes.map(zipcode => console.log(zipcode.zipcode)); + }), + ); } async function handleResponse(lookup) { try { const result = await client.send(lookup); viewResults(result); - } catch(err) { + } catch (err) { console.log(err); } -} \ No newline at end of file +} diff --git a/index.mjs b/index.mjs index 86312be..b45192c 100644 --- a/index.mjs +++ b/index.mjs @@ -32,6 +32,9 @@ import SuggestionInternationalAddressAutocomplete from "./src/international_addr import LookupUSEnrichment from "./src/us_enrichment/Lookup.js"; import ResponseUSEnrichment from "./src/us_enrichment/Response.js"; +import LookupInternationalPostalCode from "./src/international_postal_code/Lookup.js"; +import ResultInternationalPostalCode from "./src/international_postal_code/Result.js"; + export const core = { Batch, ClientBuilder, @@ -80,6 +83,11 @@ export const usEnrichment = { Response: ResponseUSEnrichment, }; +export const internationalPostalCode = { + Lookup: LookupInternationalPostalCode, + Result: ResultInternationalPostalCode, +}; + export default { core, usStreet, @@ -90,4 +98,5 @@ export default { usReverseGeo, internationalAddressAutocomplete, usEnrichment, + internationalPostalCode, }; diff --git a/src/ClientBuilder.js b/src/ClientBuilder.js index 7b65178..ae30f31 100644 --- a/src/ClientBuilder.js +++ b/src/ClientBuilder.js @@ -20,6 +20,7 @@ const InternationalStreetClient = require("./international_street/Client"); const UsReverseGeoClient = require("./us_reverse_geo/Client"); const InternationalAddressAutocompleteClient = require("./international_address_autocomplete/Client"); const UsEnrichmentClient = require("./us_enrichment/Client"); +const InternationalPostalCodeClient = require("./international_postal_code/Client"); const INTERNATIONAL_STREET_API_URI = "https://international-street.api.smarty.com/verify"; const US_AUTOCOMPLETE_PRO_API_URL = "https://us-autocomplete-pro.api.smarty.com/lookup"; @@ -30,7 +31,7 @@ const US_REVERSE_GEO_API_URL = "https://us-reverse-geo.api.smarty.com/lookup"; const INTERNATIONAL_ADDRESS_AUTOCOMPLETE_API_URL = "https://international-autocomplete.api.smarty.com/v2/lookup"; const US_ENRICHMENT_API_URL = "https://us-enrichment.api.smarty.com/lookup"; - +const INTERNATIONAL_POSTAL_CODE_API_URL = "https://international-postal-code.api.smarty.com/lookup"; /** * The ClientBuilder class helps you build a client object for one of the supported Smarty APIs.
* You can use ClientBuilder's methods to customize settings like maximum retries or timeout duration. These methods
@@ -51,7 +52,7 @@ class ClientBuilder { this.licenses = []; function noCredentialsProvided() { - return !signer instanceof StaticCredentials || !signer instanceof SharedCredentials; + return (!signer) instanceof StaticCredentials || (!signer) instanceof SharedCredentials; } } @@ -185,6 +186,10 @@ class ClientBuilder { return this.buildClient(US_ZIP_CODE_API_URL, UsZipcodeClient); } + buildInternationalPostalCodeClient() { + return this.buildClient(INTERNATIONAL_POSTAL_CODE_API_URL, InternationalPostalCodeClient); + } + buildUsAutocompleteProClient() { return this.buildClient(US_AUTOCOMPLETE_PRO_API_URL, UsAutocompleteProClient); } @@ -213,4 +218,4 @@ class ClientBuilder { } } -module.exports = ClientBuilder; \ No newline at end of file +module.exports = ClientBuilder; diff --git a/src/international_postal_code/Client.js b/src/international_postal_code/Client.js new file mode 100644 index 0000000..22ed7c3 --- /dev/null +++ b/src/international_postal_code/Client.js @@ -0,0 +1,49 @@ +const Request = require("../Request"); +const Result = require("./Result"); +const buildInputData = require("../util/buildInputData"); +const keyTranslationFormat = require("../util/apiToSDKKeyMap").internationalPostalCode; +const { UndefinedLookupError } = require("../Errors"); + +/** + * This client sends lookups to the Smarty International Postal Code API,
+ * and attaches the results to the appropriate Lookup objects. + */ +class Client { + constructor(sender) { + this.sender = sender; + } + + /** + * Sends a single lookup for validation. + * @param data A Lookup object + * @throws SmartyException + */ + send(lookup) { + if (typeof lookup === "undefined") throw new UndefinedLookupError(); + + let request = new Request(); + request.parameters = buildInputData(lookup, keyTranslationFormat); + + return new Promise((resolve, reject) => { + this.sender + .send(request) + .then((response) => { + if (response.error) reject(response.error); + + resolve(attachLookupResults(response, lookup)); + }) + .catch(reject); + }); + + function attachLookupResults(response, lookup) { + if (response.payload && Array.isArray(response.payload)) { + lookup.result = response.payload.map((r) => new Result(r)); + } else { + lookup.result = []; + } + return lookup; + } + } +} + +module.exports = Client; diff --git a/src/international_postal_code/Lookup.js b/src/international_postal_code/Lookup.js new file mode 100644 index 0000000..a205418 --- /dev/null +++ b/src/international_postal_code/Lookup.js @@ -0,0 +1,22 @@ +/** + * In addition to holding all of the input data for this lookup, this class also
+ * will contain the result of the lookup after it comes back from the API. + * @see "https://www.smarty.com/docs/cloud/international-postal-code-api#http-request-input-fields" + */ +class Lookup { + constructor(country, postalCode, administrativeArea, locality, inputId) { + this.inputId = inputId; + this.country = country; + this.postalCode = postalCode; + this.administrativeArea = administrativeArea; + this.locality = locality; + this.result = []; + this.customParameters = {}; + } + + addCustomParameter(key, value) { + this.customParameters[key] = value; + } +} + +module.exports = Lookup; diff --git a/src/international_postal_code/Result.js b/src/international_postal_code/Result.js new file mode 100644 index 0000000..ccfa723 --- /dev/null +++ b/src/international_postal_code/Result.js @@ -0,0 +1,20 @@ +/** + * @see "https://www.smarty.com/docs/cloud/international-postal-code-api#output-fields" + */ +class Result { + constructor(responseData) { + this.inputId = responseData.input_id; + this.administrativeArea = responseData.administrative_area; + this.superAdministrativeArea = responseData.super_administrative_area; + this.subAdministrativeArea = responseData.sub_administrative_area; + this.locality = responseData.locality; + this.dependentLocality = responseData.dependent_locality; + this.dependentLocalityName = responseData.dependent_locality_name; + this.doubleDependentLocality = responseData.double_dependent_locality; + this.postalCode = responseData.postal_code; + this.postalCodeExtra = responseData.postal_code_extra; + this.countryIso3 = responseData.country_iso_3; + } +} + +module.exports = Result; diff --git a/src/util/apiToSDKKeyMap.js b/src/util/apiToSDKKeyMap.js index bc370ae..ea947c5 100644 --- a/src/util/apiToSDKKeyMap.js +++ b/src/util/apiToSDKKeyMap.js @@ -72,5 +72,12 @@ module.exports = { exclude: "exclude", dataset: "dataset", data_subset: "dataSubset", + }, + internationalPostalCode: { + "input_id": "inputId", + "country": "country", + "locality": "locality", + "administrative_area": "administrativeArea", + "postal_code": "postalCode", } }; \ No newline at end of file diff --git a/src/util/buildClients.js b/src/util/buildClients.js index 42441f2..3e84776 100644 --- a/src/util/buildClients.js +++ b/src/util/buildClients.js @@ -36,6 +36,10 @@ function buildUsEnrichmentApiClient(credentials) { return instantiateClientBuilder(credentials).buildUsEnrichmentClient(); } +function buildInternationalPostalCodeApiClient(credentials) { + return instantiateClientBuilder(credentials).buildInternationalPostalCodeClient(); +} + module.exports = { usStreet: buildUsStreetApiClient, usAutocompletePro: buildUsAutocompleteProApiClient, @@ -45,4 +49,5 @@ module.exports = { usReverseGeo: buildUsReverseGeoApiClient, internationalAddressAutocomplete: buildInternationalAddressAutocompleteApiClient, usEnrichment: buildUsEnrichmentApiClient, + internationalPostalCode: buildInternationalPostalCodeApiClient, }; \ No newline at end of file diff --git a/tests/international_postal_code/test_Client.js b/tests/international_postal_code/test_Client.js new file mode 100644 index 0000000..0d79d67 --- /dev/null +++ b/tests/international_postal_code/test_Client.js @@ -0,0 +1,68 @@ +const chai = require("chai"); +const expect = chai.expect; +const Client = require("../../src/international_postal_code/Client"); +const Lookup = require("../../src/international_postal_code/Lookup"); +const MockSender = require("../fixtures/mock_senders").MockSender; +const MockSenderWithResponse = require("../fixtures/mock_senders").MockSenderWithResponse; + +describe("An International Postal Code client", function () { + it("has an inner sender.", function () { + let mockSender = new MockSender(); + let client = new Client(mockSender); + + expect(client.sender).to.deep.equal(mockSender); + }); + + it("throws an error if sending without a lookup.", function () { + let mockSender = new MockSender(); + let client = new Client(mockSender); + + expect(() => client.send()).to.throw(); + }); + + it("attaches a result from a response to a lookup.", function () { + const expectedMockPayload = [ + { + input_id: "1234", + administrative_area: "SP", + super_administrative_area: undefined, + sub_administrative_area: undefined, + locality: "São Paulo", + dependent_locality: "Casa Verde", + dependent_locality_name: undefined, + double_dependent_locality: undefined, + postal_code: "02516-040", + postal_code_extra: undefined, + country_iso_3: "BRA", + }, + ]; + let mockSender = new MockSenderWithResponse(expectedMockPayload); + const client = new Client(mockSender); + let lookup = new Lookup("Brazil", "02516-040"); + + return client.send(lookup).then(() => { + expect(lookup.result).to.be.an("array"); + expect(lookup.result.length).to.equal(1); + expect(lookup.result[0].inputId).to.equal("1234"); + expect(lookup.result[0].administrativeArea).to.equal("SP"); + expect(lookup.result[0].locality).to.equal("São Paulo"); + expect(lookup.result[0].dependentLocality).to.equal("Casa Verde"); + expect(lookup.result[0].postalCode).to.equal("02516-040"); + expect(lookup.result[0].countryIso3).to.equal("BRA"); + }); + }); + + it("handles empty results array.", function () { + const expectedMockPayload = { + results: [], + }; + let mockSender = new MockSenderWithResponse(expectedMockPayload); + const client = new Client(mockSender); + let lookup = new Lookup("Brazil", "99999-999"); + + return client.send(lookup).then(() => { + expect(lookup.result).to.be.an("array"); + expect(lookup.result.length).to.equal(0); + }); + }); +}); diff --git a/tests/international_postal_code/test_Lookup.js b/tests/international_postal_code/test_Lookup.js new file mode 100644 index 0000000..94367db --- /dev/null +++ b/tests/international_postal_code/test_Lookup.js @@ -0,0 +1,24 @@ +const chai = require("chai"); +const expect = chai.expect; +const Lookup = require("../../src/international_postal_code/Lookup"); + +describe("An International Postal Code lookup", function () { + it("correctly populates fields.", function () { + let lookup = new Lookup("Brazil", "02516-040", "SP", "Sao Paulo", "1234"); + + expect(lookup.inputId).to.equal("1234"); + expect(lookup.country).to.equal("Brazil"); + expect(lookup.postalCode).to.equal("02516-040"); + expect(lookup.administrativeArea).to.equal("SP"); + expect(lookup.locality).to.equal("Sao Paulo"); + expect(lookup.result).to.deep.equal([]); + expect(lookup.customParameters).to.deep.equal({}); + }); + + it("can add custom parameters.", function () { + let lookup = new Lookup("Brazil", "02516-040"); + lookup.addCustomParameter("test", "value"); + + expect(lookup.customParameters.test).to.equal("value"); + }); +}); diff --git a/tests/international_postal_code/test_Result.js b/tests/international_postal_code/test_Result.js new file mode 100644 index 0000000..bc873de --- /dev/null +++ b/tests/international_postal_code/test_Result.js @@ -0,0 +1,54 @@ +const chai = require("chai"); +const expect = chai.expect; +const Result = require("../../src/international_postal_code/Result"); + +describe("An International Postal Code result", function () { + it("populates with the appropriate fields.", function () { + const sampleResponse = { + input_id: "1234", + administrative_area: "SP", + super_administrative_area: "Southeast", + sub_administrative_area: "Metropolitan", + locality: "São Paulo", + dependent_locality: "Casa Verde", + dependent_locality_name: "Casa Verde District", + double_dependent_locality: "Zone 1", + postal_code: "02516-040", + postal_code_extra: "12345", + country_iso_3: "BRA", + }; + + const result = new Result(sampleResponse); + + expect(result.inputId).to.equal("1234"); + expect(result.administrativeArea).to.equal("SP"); + expect(result.superAdministrativeArea).to.equal("Southeast"); + expect(result.subAdministrativeArea).to.equal("Metropolitan"); + expect(result.locality).to.equal("São Paulo"); + expect(result.dependentLocality).to.equal("Casa Verde"); + expect(result.dependentLocalityName).to.equal("Casa Verde District"); + expect(result.doubleDependentLocality).to.equal("Zone 1"); + expect(result.postalCode).to.equal("02516-040"); + expect(result.postalCodeExtra).to.equal("12345"); + expect(result.countryIso3).to.equal("BRA"); + }); + + it("handles missing optional fields.", function () { + const sampleResponse = { + administrative_area: "SP", + locality: "São Paulo", + postal_code: "02516-040", + country_iso_3: "BRA", + }; + + const result = new Result(sampleResponse); + + expect(result.inputId).to.equal(undefined); + expect(result.superAdministrativeArea).to.equal(undefined); + expect(result.postalCodeExtra).to.equal(undefined); + expect(result.administrativeArea).to.equal("SP"); + expect(result.locality).to.equal("São Paulo"); + expect(result.postalCode).to.equal("02516-040"); + expect(result.countryIso3).to.equal("BRA"); + }); +});