Skip to content

chore(config-resolver): refactor EndpointsConfig into multiple files #2705

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

Merged
merged 9 commits into from
Aug 25, 2021
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 0 additions & 144 deletions packages/config-resolver/src/EndpointsConfig.spec.ts

This file was deleted.

69 changes: 0 additions & 69 deletions packages/config-resolver/src/EndpointsConfig.ts

This file was deleted.

32 changes: 32 additions & 0 deletions packages/config-resolver/src/endpointsConfig/configurations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Endpoint, Provider, RegionInfoProvider, UrlParser } from "@aws-sdk/types";

export interface EndpointsInputConfig {
/**
* The fully qualified endpoint of the webservice. This is only required when using a custom endpoint (for example, when using a local version of S3).
*/
endpoint?: string | Endpoint | Provider<Endpoint>;

/**
* Whether TLS is enabled for requests.
*/
tls?: boolean;
}

export interface PreviouslyResolved {
regionInfoProvider: RegionInfoProvider;
urlParser: UrlParser;
region: Provider<string>;
}

export interface EndpointsResolvedConfig extends Required<EndpointsInputConfig> {
/**
* Resolved value for input {@link EndpointsResolvedConfig.endpoint}
*/
endpoint: Provider<Endpoint>;

/**
* Whether the endpoint is specified by caller.
* @internal
*/
isCustomEndpoint: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { getEndpointFromRegion } from "./getEndpointFromRegion";

describe(getEndpointFromRegion.name, () => {
const mockRegion = jest.fn();
const mockUrlParser = jest.fn();
const mockRegionInfoProvider = jest.fn();

const mockInput = { region: mockRegion, urlParser: mockUrlParser, regionInfoProvider: mockRegionInfoProvider };

const mockRegionValue = "mockRegion";
const mockEndpoint = {
protocol: "http:",
hostname: "localhost",
path: "/",
};
const mockRegionInfo = { hostname: "mockHostname" };

beforeEach(() => {
mockRegion.mockResolvedValue(mockRegionValue);
mockUrlParser.mockResolvedValue(mockEndpoint);
mockRegionInfoProvider.mockResolvedValue(mockRegionInfo);
});

afterEach(() => {
expect(mockRegion).toHaveBeenCalledTimes(1);
jest.clearAllMocks();
});

describe("tls", () => {
afterEach(() => {
expect(mockRegionInfoProvider).toHaveBeenCalledWith(mockRegionValue);
});

it("uses protocol https when not defined", async () => {
await getEndpointFromRegion(mockInput);
expect(mockUrlParser).toHaveBeenCalledTimes(1);
expect(mockUrlParser).toHaveBeenCalledWith(`https://${mockRegionInfo.hostname}`);
});

it.each([
["http:", false],
["https:", true],
])("uses protocol %s when set to %s", async (protocol, tls) => {
await getEndpointFromRegion({ ...mockInput, tls });
expect(mockUrlParser).toHaveBeenCalledTimes(1);
expect(mockUrlParser).toHaveBeenCalledWith(`${protocol}//${mockRegionInfo.hostname}`);
});
});

describe("throws if region is invalid", () => {
const errorMsg = "Invalid region in client config";
it.each([
"",
"has_underscore",
"-starts-with-dash",
"ends-with-dash-",
"-starts-and-ends-with-dash-",
"-",
"a-",
"c0nt@in$-$ymb01$",
"a".repeat(64),
])("region: %s", async (region) => {
mockRegion.mockResolvedValue(region);
try {
await getEndpointFromRegion(mockInput);
fail(`expected Error: ${errorMsg}`);
} catch (error) {
expect(error.message).toEqual(errorMsg);
}
expect(mockRegionInfoProvider).not.toHaveBeenCalled();
expect(mockUrlParser).not.toHaveBeenCalled();
});
});

it("throws if hostname is not returned by regionInfoProvider", async () => {
mockRegionInfoProvider.mockResolvedValue({});
const errorMsg = "Cannot resolve hostname from client config";
try {
await getEndpointFromRegion(mockInput);
fail(`expected Error: ${errorMsg}`);
} catch (error) {
expect(error.message).toEqual(errorMsg);
}
expect(mockRegionInfoProvider).toHaveBeenCalledWith(mockRegionValue);
expect(mockUrlParser).not.toHaveBeenCalled();
});

it("returns parsed endpoint", async () => {
const endpoint = await getEndpointFromRegion(mockInput);
expect(endpoint).toEqual(mockEndpoint);
expect(mockRegionInfoProvider).toHaveBeenCalledWith(mockRegionValue);
expect(mockUrlParser).toHaveBeenCalledWith(`https://${mockRegionInfo.hostname}`);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { EndpointsInputConfig, PreviouslyResolved } from "./configurations";

export const getEndpointFromRegion = async (input: EndpointsInputConfig & PreviouslyResolved) => {
const { tls = true } = input;
const region = await input.region();

const dnsHostRegex = new RegExp(/^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/);
if (!dnsHostRegex.test(region)) {
throw new Error("Invalid region in client config");
}

const { hostname } = (await input.regionInfoProvider(region)) ?? {};
if (!hostname) {
throw new Error("Cannot resolve hostname from client config");
}

return input.urlParser(`${tls ? "https:" : "http:"}//${hostname}`);
};
2 changes: 2 additions & 0 deletions packages/config-resolver/src/endpointsConfig/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./configurations";
export * from "./resolveEndpointsConfig";
Loading