Skip to content

Commit c04e473

Browse files
committed
fix: add fallback resolution
1 parent 3d29f4e commit c04e473

File tree

5 files changed

+95
-41
lines changed

5 files changed

+95
-41
lines changed

packages/middleware-endpoint/src/adaptors/createConfigValueProvider.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,31 @@ describe(createConfigValueProvider.name, () => {
5858
expect(await createConfigValueProvider("v1", "endpoint", config)()).toEqual(sampleUrl);
5959
expect(await createConfigValueProvider("v2", "endpoint", config)()).toEqual(sampleUrl);
6060
});
61+
62+
it("should prioritize clientContextParams over direct properties", async () => {
63+
const config = {
64+
apiKey: "direct-api-key",
65+
clientContextParams: {
66+
apiKey: "nested-api-key",
67+
},
68+
};
69+
expect(await createConfigValueProvider("apiKey", "apiKey", config)()).toEqual("nested-api-key");
70+
});
71+
72+
it("should fall back to direct property when clientContextParams is not provided", async () => {
73+
const config = {
74+
customParam: "direct-value",
75+
};
76+
expect(await createConfigValueProvider("customParam", "customParam", config)()).toEqual("direct-value");
77+
});
78+
79+
it("should fall back to direct property when clientContextParams exists but param is not in it", async () => {
80+
const config = {
81+
customParam: "direct-value",
82+
clientContextParams: {
83+
otherParam: "other-value",
84+
},
85+
};
86+
expect(await createConfigValueProvider("customParam", "customParam", config)()).toEqual("direct-value");
87+
});
6188
});

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ export const createConfigValueProvider = <Config extends Record<string, unknown>
1818
config: Config
1919
) => {
2020
const configProvider = async () => {
21-
const configValue: unknown = config[configKey] ?? config[canonicalEndpointParamKey];
21+
// Check clientContextParams first for client context parameters
22+
const clientContextParams = config.clientContextParams as Record<string, unknown> | undefined;
23+
const nestedValue: unknown = clientContextParams?.[configKey] ?? clientContextParams?.[canonicalEndpointParamKey];
24+
25+
// Fall back to direct config properties
26+
const configValue: unknown = nestedValue ?? config[configKey] ?? config[canonicalEndpointParamKey];
27+
2228
if (typeof configValue === "function") {
2329
return configValue();
2430
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ export const resolveParams = async <
7878
instructionsSupplier: EndpointParameterInstructionsSupplier,
7979
clientConfig: Partial<EndpointResolvedConfig<T>> & Config
8080
) => {
81+
// Initialize clientContextParams to empty object if undefined
82+
// when accessing nested properties during parameter resolution
83+
const config = clientConfig as typeof clientConfig & { clientContextParams?: Record<string, unknown> };
84+
if (config.clientContextParams === undefined) {
85+
config.clientContextParams = {};
86+
}
87+
8188
const endpointParams: EndpointParameters = {};
8289
const instructions: EndpointParameterInstructions = instructionsSupplier?.getEndpointParameterInstructions?.() || {};
8390

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/EndpointsV2Generator.java

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -111,29 +111,29 @@ private void generateEndpointParameters() {
111111
this.delegator.useFileWriter(
112112
Paths.get(CodegenUtils.SOURCE_FOLDER, ENDPOINT_FOLDER, ENDPOINT_PARAMETERS_FILE).toString(),
113113
writer -> {
114-
writer.addTypeImport("EndpointParameters", "__EndpointParameters", TypeScriptDependency.SMITHY_TYPES);
115-
writer.addTypeImport("Provider", null, TypeScriptDependency.SMITHY_TYPES);
114+
writer.addImport("EndpointParameters", "__EndpointParameters", TypeScriptDependency.SMITHY_TYPES);
115+
writer.addImport("Provider", null, TypeScriptDependency.SMITHY_TYPES);
116+
Map<String, String> clientContextParams =
117+
ruleSetParameterFinder.getClientContextParams();
118+
Map<String, String> builtInParams = ruleSetParameterFinder.getBuiltInParams();
119+
builtInParams.keySet().removeIf(OmitEndpointParams::isOmitted);
120+
Set<String> knownConfigKeys = Set.of(
121+
"apiKey", "retryStrategy", "requestHandler");
122+
// Generate clientContextParams with all params excluding built-ins
123+
Map<String, String> customerContextParams = new HashMap<>();
124+
for (Map.Entry<String, String> entry : clientContextParams.entrySet()) {
125+
if (!builtInParams.containsKey(entry.getKey())) {
126+
customerContextParams.put(entry.getKey(), entry.getValue());
127+
}
128+
}
116129

117130
writer.writeDocs("@public");
118131
writer.openBlock(
119132
"export interface ClientInputEndpointParameters {",
120133
"}",
121134
() -> {
122-
Map<String, String> clientContextParams =
123-
ruleSetParameterFinder.getClientContextParams();
124-
Map<String, String> builtInParams = ruleSetParameterFinder.getBuiltInParams();
125-
builtInParams.keySet().removeIf(OmitEndpointParams::isOmitted);
126-
Set<String> knownConfigKeys = Set.of(
127-
"apiKey", "retryStrategy", "requestHandler");
128-
// Generate clientContextParams with all params excluding built-ins
129-
Map<String, String> customerContextParams = new HashMap<>();
130-
for (Map.Entry<String, String> entry : clientContextParams.entrySet()) {
131-
if (!builtInParams.containsKey(entry.getKey())) {
132-
customerContextParams.put(entry.getKey(), entry.getValue());
133-
}
134-
}
135135
if (!customerContextParams.isEmpty()) {
136-
writer.write("clientContextParams: {");
136+
writer.write("clientContextParams?: {");
137137
writer.indent();
138138
ObjectNode ruleSet = endpointRuleSetTrait.getRuleSet().expectObjectNode();
139139
ruleSet.getObjectMember("parameters").ifPresent(parameters -> {
@@ -160,45 +160,30 @@ private void generateEndpointParameters() {
160160
);
161161

162162
writer.write("");
163-
writer.writeDocs("@public");
164-
writer.openBlock(
165-
"""
166-
export type ClientResolvedEndpointParameters = Omit<ClientInputEndpointParameters, "endpoint"> & {
167-
""",
163+
// Build Omit type - omit endpoint, and clientContextParams if exists
164+
String omitFields = customerContextParams.isEmpty()
165+
? "\"endpoint\""
166+
: "\"endpoint\" | \"clientContextParams\"";
167+
writer.openBlock(
168+
"export type ClientResolvedEndpointParameters = "
169+
+ "Omit<ClientInputEndpointParameters, " + omitFields + "> & {",
168170
"};",
169171
() -> {
170172
writer.write("defaultSigningName: string;");
171-
// Add clientContextParams with same structure as input
172-
Map<String, String> clientContextParams = ruleSetParameterFinder.getClientContextParams();
173-
Map<String, String> customerContextParams = new HashMap<>();
174-
Map<String, String> builtInParams = ruleSetParameterFinder.getBuiltInParams();
175-
for (Map.Entry<String, String> entry : clientContextParams.entrySet()) {
176-
if (!builtInParams.containsKey(entry.getKey())) {
177-
customerContextParams.put(entry.getKey(), entry.getValue());
178-
}
179-
}
180173
if (!customerContextParams.isEmpty()) {
181174
writer.write("clientContextParams: {");
182175
writer.indent();
183176
ObjectNode ruleSet = endpointRuleSetTrait.getRuleSet().expectObjectNode();
184177
ruleSet.getObjectMember("parameters").ifPresent(parameters -> {
185178
parameters.accept(new RuleSetParametersVisitor(
186-
writer, customerContextParams, false));
179+
writer, customerContextParams, true));
187180
});
188181
writer.dedent();
189182
writer.write("};");
190183
}
191184
}
192185
);
193186
// Generate clientContextParamDefaults only if there are customer context params
194-
Map<String, String> clientContextParams = ruleSetParameterFinder.getClientContextParams();
195-
Map<String, String> builtInParams = ruleSetParameterFinder.getBuiltInParams();
196-
Map<String, String> customerContextParams = new HashMap<>();
197-
for (Map.Entry<String, String> entry : clientContextParams.entrySet()) {
198-
if (!builtInParams.containsKey(entry.getKey())) {
199-
customerContextParams.put(entry.getKey(), entry.getValue());
200-
}
201-
}
202187
if (!customerContextParams.isEmpty()) {
203188
// Check if any parameters have default values
204189
boolean hasDefaults = false;
@@ -256,6 +241,34 @@ private void generateEndpointParameters() {
256241
"defaultSigningName: \"$L\",",
257242
settings.getDefaultSigningName()
258243
);
244+
// Only generate clientContextParams if there are customer context params
245+
if (!customerContextParams.isEmpty()) {
246+
// Initialize clientContextParams if undefined to satisfy type requirements
247+
// Check if we have defaults to merge
248+
boolean hasDefaultsForResolve = false;
249+
if (ruleSet.getObjectMember("parameters").isPresent()) {
250+
ObjectNode parameters = ruleSet.getObjectMember("parameters")
251+
.get().expectObjectNode();
252+
for (Map.Entry<String, String> entry : customerContextParams.entrySet()) {
253+
String paramName = entry.getKey();
254+
ObjectNode paramNode = parameters.getObjectMember(paramName).orElse(null);
255+
if (paramNode != null && paramNode.containsMember("default")) {
256+
hasDefaultsForResolve = true;
257+
break;
258+
}
259+
}
260+
}
261+
if (hasDefaultsForResolve) {
262+
writer.write(
263+
"clientContextParams: Object.assign(clientContextParamDefaults, "
264+
+ "options.clientContextParams ?? {}),"
265+
);
266+
} else {
267+
writer.write(
268+
"clientContextParams: options.clientContextParams ?? {},"
269+
);
270+
}
271+
}
259272
});
260273
}
261274
);

smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/endpointsV2/EndpointsV2GeneratorTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,13 @@ public void containsExtraContextParameter() {
5656
return Object.assign(options, {
5757
stage: options.stage ?? "production",
5858
defaultSigningName: "",
59+
clientContextParams: Object.assign(clientContextParamDefaults, options.clientContextParams ?? {}),
5960
});
6061
"""));
6162
assertThat(endpointParameters, containsString(
6263
"""
6364
export interface ClientInputEndpointParameters {
64-
clientContextParams: {
65+
clientContextParams?: {
6566
region?: string | undefined | Provider<string | undefined>;
6667
stage?: string | undefined | Provider<string | undefined>;
6768
};

0 commit comments

Comments
 (0)