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
Original file line number Diff line number Diff line change
Expand Up @@ -404,17 +404,8 @@ private void generateUriSpec(GenerationContext context, OperationShape operation
writer.write("{ type: 'query_literal', key: $S, value: $S },", e.getKey(), e.getValue());
}
}
operation
.getInput()
.ifPresent(inputId -> {
StructureShape inputShape = context.getModel().expectShape(inputId, StructureShape.class);
for (MemberShape ms : inputShape.members()) {
if (ms.isRequired() && ms.hasTrait(HttpQueryTrait.class)) {
HttpQueryTrait queryTrait = ms.expectTrait(HttpQueryTrait.class);
writer.write("{ type: 'query', key: $S },", queryTrait.getValue());
}
}
});
// Required @httpQuery members are validated post-routing, not used for routing.
// Including them here would cause UnknownOperationException instead of ValidationException.
});
writer.writeInline("{ service: $S, operation: $S }", serviceName, operationName);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ describe("simple matching", () => {
new HttpRequest({ method: "DELETE", path: "/", query: { foo: "bar", baz: "quux" } }),
new HttpRequest({ method: "DELETE", path: "/", query: { foo: "bar", baz: null } }),
new HttpRequest({ method: "DELETE", path: "", query: { foo: "bar", baz: ["quux", "grault"] } }),
// "query" type segments (from @required @httpQuery) are not used for routing,
// so missing "baz" still routes to Delete — validation catches it later
new HttpRequest({ method: "DELETE", path: "/", query: { foo: "bar" } }),
],
"Test#QueryKeyOnly": [
new HttpRequest({ method: "GET", path: "/query_key_only", query: { foo: "bar" } }),
Expand Down Expand Up @@ -115,7 +118,6 @@ describe("simple matching", () => {
new HttpRequest({ method: "GET", path: "/mg/a/y/z/a" }),
new HttpRequest({ method: "GET", path: "/mg/a/y/a" }),
new HttpRequest({ method: "GET", path: "/mg/a/b/z/c" }),
new HttpRequest({ method: "DELETE", path: "/", query: { foo: "bar" } }),
new HttpRequest({ method: "DELETE", path: "/", query: { baz: "quux" } }),
new HttpRequest({ method: "DELETE", path: "/" }),
];
Expand Down
25 changes: 15 additions & 10 deletions smithy-typescript-ssdk-libs/server-common/src/httpbinding/mux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export class UriSpec<S extends string, O extends string> {
private readonly method: string;
private readonly pathSegments: (PathLiteralSegment | PathLabelSegment | GreedySegment)[];
private readonly querySegments: (QueryLiteralSegment | QuerySegment)[];
// Only query_literal segments participate in routing. "query" segments
// (from @required @httpQuery) are validated post-routing so they don't
// cause UnknownOperationException when missing.
private readonly routingQuerySegments: QueryLiteralSegment[];
readonly rank: number;
readonly target: ServiceCoordinate<S, O>;

Expand All @@ -56,7 +60,10 @@ export class UriSpec<S extends string, O extends string> {
this.method = method;
this.pathSegments = pathSegments;
this.querySegments = querySegments;
this.rank = this.pathSegments.length + this.querySegments.length;
this.routingQuerySegments = this.querySegments.filter(
(s): s is QueryLiteralSegment => s.type === "query_literal"
);
this.rank = this.pathSegments.length + this.routingQuerySegments.length;
this.target = target;
}

Expand Down Expand Up @@ -108,27 +115,25 @@ export class UriSpec<S extends string, O extends string> {
return false;
}

if (this.querySegments.length === 0) {
if (this.routingQuerySegments.length === 0) {
return true;
}

if (!req.query) {
return false;
}

for (const querySegment of this.querySegments) {
for (const querySegment of this.routingQuerySegments) {
if (!(querySegment.key in req.query)) {
return false;
}
if (querySegment.type === "query_literal") {
const input_query_value = req.query[querySegment.key];
if (Array.isArray(input_query_value)) {
if (querySegment.value && !input_query_value.includes(querySegment.value)) {
return false;
}
} else if (querySegment.value && querySegment.value !== input_query_value) {
const input_query_value = req.query[querySegment.key];
if (Array.isArray(input_query_value)) {
if (querySegment.value && !input_query_value.includes(querySegment.value)) {
return false;
}
} else if (querySegment.value && querySegment.value !== input_query_value) {
return false;
}
}
return true;
Expand Down