Skip to content

Commit 77788f9

Browse files
authored
fix(endpoint): misc fixes for endpoints 2.0 based on service unit tests (#4002)
* fix(endpoint): misc fixes for endpoints 2.0 based on service unit test cases * fix(endpoint): remove debug code
1 parent c20ed87 commit 77788f9

File tree

11 files changed

+182
-51
lines changed

11 files changed

+182
-51
lines changed

packages/middleware-endpoint/src/adaptors/getEndpointFromInstructions.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { EndpointParameters, EndpointV2, HandlerExecutionContext } from "@aws-sdk/types";
22

33
import { EndpointResolvedConfig } from "../resolveEndpointConfig";
4+
import { resolveParamsForS3 } from "../service-customizations";
45
import { EndpointParameterInstructions } from "../types";
56

67
export type EndpointParameterInstructionsSupplier = Partial<{
@@ -31,13 +32,28 @@ export const getEndpointFromInstructions = async <
3132
clientConfig: Partial<EndpointResolvedConfig<T>> & Config,
3233
context?: HandlerExecutionContext
3334
): Promise<EndpointV2> => {
34-
const endpointParams: EndpointParameters = {};
35-
const instructions: EndpointParameterInstructions =
36-
(instructionsSupplier.getEndpointParameterInstructions || (() => null))() || {};
35+
const endpointParams = await resolveParams(commandInput, instructionsSupplier, clientConfig);
3736

3837
if (typeof clientConfig.endpointProvider !== "function") {
3938
throw new Error("config.endpointProvider is not set.");
4039
}
40+
const endpoint: EndpointV2 = clientConfig.endpointProvider!(endpointParams as T, context);
41+
42+
return endpoint;
43+
};
44+
45+
export const resolveParams = async <
46+
T extends EndpointParameters,
47+
CommandInput extends Record<string, unknown>,
48+
Config extends Record<string, unknown>
49+
>(
50+
commandInput: CommandInput,
51+
instructionsSupplier: EndpointParameterInstructionsSupplier,
52+
clientConfig: Partial<EndpointResolvedConfig<T>> & Config
53+
) => {
54+
const endpointParams: EndpointParameters = {};
55+
const instructions: EndpointParameterInstructions =
56+
(instructionsSupplier.getEndpointParameterInstructions || (() => null))() || {};
4157

4258
for (const [name, instruction] of Object.entries(instructions)) {
4359
switch (instruction.type) {
@@ -49,25 +65,35 @@ export const getEndpointFromInstructions = async <
4965
break;
5066
case "clientContextParams":
5167
case "builtInParams":
52-
endpointParams[name] = await createConfigProvider<Config>(instruction.name, clientConfig)();
68+
endpointParams[name] = await createConfigProvider<Config>(instruction.name, name, clientConfig)();
5369
break;
5470
default:
5571
throw new Error("Unrecognized endpoint parameter instruction: " + JSON.stringify(instruction));
5672
}
5773
}
5874

59-
const endpoint: EndpointV2 = clientConfig.endpointProvider!(endpointParams as T, context);
75+
if (Object.keys(instructions).length === 0) {
76+
Object.assign(endpointParams, clientConfig);
77+
}
6078

61-
return endpoint;
79+
if (String(clientConfig.serviceId).toLowerCase() === "s3") {
80+
resolveParamsForS3(endpointParams);
81+
}
82+
83+
return endpointParams;
6284
};
6385

6486
/**
6587
* Normalize some key of the client config to an async provider.
6688
* @private
6789
*/
68-
const createConfigProvider = <Config extends Record<string, unknown>>(configKey: string, config: Config) => {
90+
const createConfigProvider = <Config extends Record<string, unknown>>(
91+
configKey: string,
92+
canonicalEndpointParamKey: string,
93+
config: Config
94+
) => {
6995
const configProvider = async () => {
70-
const configValue: unknown = config[configKey];
96+
const configValue: unknown = config[configKey] || config[canonicalEndpointParamKey];
7197
if (typeof configValue === "function") {
7298
return configValue();
7399
}

packages/middleware-endpoint/src/endpointMiddleware.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,8 @@ import {
1212

1313
import { getEndpointFromInstructions } from "./adaptors/getEndpointFromInstructions";
1414
import { EndpointResolvedConfig } from "./resolveEndpointConfig";
15-
import { s3Customizations } from "./service-customizations";
1615
import { EndpointParameterInstructions } from "./types";
1716

18-
export type PreviouslyResolvedServiceId = {
19-
serviceId?: string;
20-
};
2117

2218
/**
2319
* @private
@@ -26,7 +22,7 @@ export const endpointMiddleware = <T extends EndpointParameters>({
2622
config,
2723
instructions,
2824
}: {
29-
config: EndpointResolvedConfig<T> & PreviouslyResolvedServiceId;
25+
config: EndpointResolvedConfig<T>;
3026
instructions: EndpointParameterInstructions;
3127
}): SerializeMiddleware<any, any> => {
3228
return <Output extends MetadataBearer>(
@@ -54,10 +50,6 @@ export const endpointMiddleware = <T extends EndpointParameters>({
5450
context["signing_service"] = authScheme.signingName;
5551
}
5652

57-
if (config.serviceId === "S3") {
58-
await s3Customizations(config, instructions, args, context);
59-
}
60-
6153
return next({
6254
...args,
6355
});

packages/middleware-endpoint/src/getEndpointPlugin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { serializerMiddlewareOption } from "@aws-sdk/middleware-serde";
22
import { EndpointParameters, Pluggable, RelativeMiddlewareOptions, SerializeHandlerOptions } from "@aws-sdk/types";
33

4-
import { endpointMiddleware, PreviouslyResolvedServiceId } from "./endpointMiddleware";
4+
import { endpointMiddleware } from "./endpointMiddleware";
55
import { EndpointResolvedConfig } from "./resolveEndpointConfig";
66
import { EndpointParameterInstructions } from "./types";
77

@@ -15,7 +15,7 @@ export const endpointMiddlewareOptions: SerializeHandlerOptions & RelativeMiddle
1515
};
1616

1717
export const getEndpointPlugin = <T extends EndpointParameters>(
18-
config: EndpointResolvedConfig<T> & PreviouslyResolvedServiceId,
18+
config: EndpointResolvedConfig<T>,
1919
instructions: EndpointParameterInstructions
2020
): Pluggable<any, any> => ({
2121
applyToStack: (clientStack) => {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { EndpointParameterInstructionsSupplier } from "../adaptors";
2+
3+
export interface EndpointTestCase {
4+
documentation?: string;
5+
params?: Record<string, boolean | string | unknown>;
6+
tags?: string[];
7+
expect: EndpointExpectation | ErrorExpectation;
8+
operationInputs?: OperationInput[];
9+
}
10+
11+
export type OperationInput = {
12+
operationName: string;
13+
operationParams?: Record<string, string | boolean | unknown>;
14+
builtInParams?: Record<string, string | boolean | unknown>;
15+
clientParams?: Record<string, string | boolean | unknown>;
16+
};
17+
18+
export type EndpointExpectation = {
19+
endpoint: {
20+
url: string;
21+
properties?: Record<string, unknown>;
22+
headers?: Record<string, string[]>;
23+
};
24+
};
25+
26+
export type ErrorExpectation = {
27+
error: string;
28+
};
29+
30+
export interface ServiceNamespace {
31+
[Command: string]: EndpointParameterInstructionsSupplier;
32+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import * as S3Namespace from "@aws-sdk/client-s3";
2+
import { EndpointV2 } from "@aws-sdk/types";
3+
4+
// import { defaultEndpointResolver } from "../../../../clients/client-s3/src/endpoint/endpointResolver";
5+
const defaultEndpointResolver = (...args: any): any => {};
6+
7+
import { resolveParams } from "../adaptors";
8+
import { EndpointExpectation, EndpointTestCase, ErrorExpectation, ServiceNamespace } from "./integration-test-types";
9+
import customTests from "./s3/custom-tests.json";
10+
11+
describe("endpoints 2.0 service integration", () => {
12+
describe("s3", () => {
13+
it("placeholder", () => {});
14+
runTestCases(customTests, S3Namespace as ServiceNamespace);
15+
});
16+
});
17+
18+
function runTestCases({ testCases }: { testCases: EndpointTestCase[] }, service: ServiceNamespace) {
19+
const start = 0; //67;
20+
const end = 100; //68;
21+
for (const testCase of testCases.slice(start, end)) {
22+
runTestCase(testCase, service);
23+
}
24+
}
25+
26+
async function runTestCase(testCase: EndpointTestCase, service: ServiceNamespace) {
27+
const { documentation, params = {}, expect: expectation, operationInputs } = testCase;
28+
29+
it(documentation || "undocumented testcase", async () => {
30+
if (isEndpointExpectation(expectation)) {
31+
const { endpoint } = expectation;
32+
if (operationInputs) {
33+
for (const operationInput of operationInputs) {
34+
const { operationName, operationParams = {} } = operationInput;
35+
const endpointParams = await resolveParams(operationParams, service[`${operationName}Command`], params);
36+
const observed = defaultEndpointResolver(endpointParams as any);
37+
console.log("params were", endpointParams);
38+
assertEndpointResolvedCorrectly(endpoint, observed);
39+
}
40+
} else {
41+
const endpointParams = await resolveParams({}, {}, params);
42+
const observed = defaultEndpointResolver(endpointParams as any);
43+
console.log("params were", endpointParams);
44+
assertEndpointResolvedCorrectly(endpoint, observed);
45+
}
46+
}
47+
if (isErrorExpectation(expectation)) {
48+
const { error } = expectation;
49+
const pass = (err) => err;
50+
const normalizeQuotes = (s) => s.replace(/`/g, "");
51+
if (operationInputs) {
52+
for (const operationInput of operationInputs) {
53+
const { operationName, operationParams = {} } = operationInput;
54+
const endpointParams = await resolveParams(operationParams, service[`${operationName}Command`], {
55+
...params,
56+
endpointProvider: defaultEndpointResolver,
57+
});
58+
const observedError = await (async () => defaultEndpointResolver(endpointParams as any))().catch(pass);
59+
expect(observedError).not.toBeUndefined();
60+
// expect(normalizeQuotes(String(observedError))).toContain(normalizeQuotes(error));
61+
}
62+
} else {
63+
const endpointParams = await resolveParams({}, {}, params);
64+
const observedError = await (async () => defaultEndpointResolver(endpointParams as any))().catch(pass);
65+
console.error(observedError);
66+
expect(observedError).not.toBeUndefined();
67+
// expect(normalizeQuotes(String(observedError))).toContain(normalizeQuotes(error));
68+
}
69+
}
70+
});
71+
}
72+
73+
function assertEndpointResolvedCorrectly(expected: EndpointExpectation["endpoint"], observed: EndpointV2) {
74+
const { url, headers, properties } = expected;
75+
const { authSchemes } = properties || {};
76+
if (url) {
77+
expect(observed.url.href).toContain(url);
78+
expect(Math.abs(observed.url.href.length - url.length)).toBeLessThan(2);
79+
}
80+
if (headers) {
81+
expect(observed.headers).toEqual(headers);
82+
}
83+
if (authSchemes) {
84+
// expect(observed.properties?.authSchemes).toEqual(authSchemes);
85+
}
86+
}
87+
88+
function isEndpointExpectation(expectation: object): expectation is EndpointExpectation {
89+
return "endpoint" in expectation;
90+
}
91+
92+
function isErrorExpectation(expectation: object): expectation is ErrorExpectation {
93+
return "error" in expectation;
94+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"testCases": [],
3+
"version": "1.0"
4+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export * from './s3'
1+
export * from "./s3";

packages/middleware-endpoint/src/service-customizations/s3.ts

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,13 @@
1-
import { EndpointParameters, HandlerExecutionContext, SerializeHandlerArguments } from "@aws-sdk/types";
1+
import { EndpointParameters } from "@aws-sdk/types";
22

3-
import { getEndpointFromInstructions } from "../adaptors";
4-
import { PreviouslyResolvedServiceId } from "../endpointMiddleware";
5-
import { EndpointResolvedConfig } from "../resolveEndpointConfig";
6-
import { EndpointParameterInstructions } from "../types";
7-
8-
/**
9-
* Bucket name DNS compatibility customization.
10-
*/
11-
export const s3Customizations = async <T extends EndpointParameters>(
12-
config: EndpointResolvedConfig<T> & PreviouslyResolvedServiceId,
13-
instructions: EndpointParameterInstructions,
14-
args: SerializeHandlerArguments<any>,
15-
context: HandlerExecutionContext
16-
) => {
17-
const endpoint = context.endpointV2;
18-
const bucket: string | undefined = args.input?.Bucket || void 0;
19-
20-
if (!endpoint || !bucket) {
21-
return;
22-
}
3+
export const resolveParamsForS3 = async (endpointParams: EndpointParameters) => {
4+
const bucket = (endpointParams?.Bucket as string) || "";
235

246
if (!isDnsCompatibleBucketName(bucket) || bucket.indexOf(".") !== -1) {
25-
context.endpointV2 = await getEndpointFromInstructions(
26-
args.input,
27-
{
28-
getEndpointParameterInstructions: () => instructions,
29-
},
30-
{ ...config, forcePathStyle: true }
31-
);
7+
endpointParams.ForcePathStyle = true;
328
}
9+
10+
return endpointParams;
3311
};
3412

3513
const DOMAIN_PATTERN = /^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$/;

packages/util-endpoints/src/lib/isValidHostLabel.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { isValidHostLabel } from "./isValidHostLabel";
22

33
describe(isValidHostLabel.name, () => {
44
const testCases: Array<[boolean, string]> = [
5-
[false, "01010"],
5+
[true, "01010"],
66
[true, "abc"],
77
[true, "A0c"],
88
[false, "A0c-"],

packages/util-endpoints/src/lib/isValidHostLabel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const VALID_HOST_LABEL_REGEX = new RegExp(`^(?![0-9]+$)(?!.*-$)(?!-)[a-zA-Z0-9-]{1,63}$`);
1+
const VALID_HOST_LABEL_REGEX = new RegExp(`^(?!.*-$)(?!-)[a-zA-Z0-9-]{1,63}$`);
22

33
/**
44
* Evaluates whether one or more string values are valid host labels per RFC 1123.

packages/util-endpoints/src/utils/getEndpointUrl.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ import { evaluateExpression } from "./evaluateExpression";
55
export const getEndpointUrl = (endpointUrl: Expression, options: EvaluateOptions): URL => {
66
const expression = evaluateExpression(endpointUrl, "Endpoint URL", options);
77
if (typeof expression === "string") {
8-
return new URL(expression);
8+
try {
9+
return new URL(expression);
10+
} catch (error) {
11+
console.error(`Failed to construct URL with ${expression}`, error);
12+
throw error;
13+
}
914
}
1015
throw new EndpointError(`Endpoint URL must be a string, got ${typeof expression}`);
1116
};

0 commit comments

Comments
 (0)