Skip to content

Commit 1638f13

Browse files
chore(client): improve path param validation
1 parent b8e1e4b commit 1638f13

File tree

4 files changed

+183
-16
lines changed

4 files changed

+183
-16
lines changed

src/internal/uploads.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export const multipartFormRequestOptions = async (
9090
return { ...opts, body: await createForm(opts.body, fetch) };
9191
};
9292

93-
const supportsFormDataMap = /** @__PURE__ */ new WeakMap<Fetch, Promise<boolean>>();
93+
const supportsFormDataMap = /* @__PURE__ */ new WeakMap<Fetch, Promise<boolean>>();
9494

9595
/**
9696
* node-fetch doesn't support the global FormData object in recent node versions. Instead of sending

src/internal/utils/log.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const noopLogger = {
5858
debug: noop,
5959
};
6060

61-
let cachedLoggers = /** @__PURE__ */ new WeakMap<Logger, [LogLevel, Logger]>();
61+
let cachedLoggers = /* @__PURE__ */ new WeakMap<Logger, [LogLevel, Logger]>();
6262

6363
export function loggerFor(client: BaseAnthropic): Logger {
6464
const logger = client.logger;

src/internal/utils/path.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,43 @@ export function encodeURIPath(str: string) {
1212
return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
1313
}
1414

15+
const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
16+
1517
export const createPathTagFunction = (pathEncoder = encodeURIPath) =>
1618
function path(statics: readonly string[], ...params: readonly unknown[]): string {
1719
// If there are no params, no processing is needed.
1820
if (statics.length === 1) return statics[0]!;
1921

2022
let postPath = false;
23+
const invalidSegments = [];
2124
const path = statics.reduce((previousValue, currentValue, index) => {
2225
if (/[?#]/.test(currentValue)) {
2326
postPath = true;
2427
}
25-
return (
26-
previousValue +
27-
currentValue +
28-
(index === params.length ? '' : (postPath ? encodeURIComponent : pathEncoder)(String(params[index])))
29-
);
28+
const value = params[index];
29+
let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value);
30+
if (
31+
index !== params.length &&
32+
(value == null ||
33+
(typeof value === 'object' &&
34+
// handle values from other realms
35+
value.toString ===
36+
Object.getPrototypeOf(Object.getPrototypeOf((value as any).hasOwnProperty ?? EMPTY) ?? EMPTY)
37+
?.toString))
38+
) {
39+
encoded = value + '';
40+
invalidSegments.push({
41+
start: previousValue.length + currentValue.length,
42+
length: encoded.length,
43+
error: `Value of type ${Object.prototype.toString
44+
.call(value)
45+
.slice(8, -1)} is not a valid path parameter`,
46+
});
47+
}
48+
return previousValue + currentValue + (index === params.length ? '' : encoded);
3049
}, '');
3150

3251
const pathOnly = path.split(/[?#]/, 1)[0]!;
33-
const invalidSegments = [];
3452
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
3553
let match;
3654

@@ -39,9 +57,12 @@ export const createPathTagFunction = (pathEncoder = encodeURIPath) =>
3957
invalidSegments.push({
4058
start: match.index,
4159
length: match[0].length,
60+
error: `Value "${match[0]}" can\'t be safely passed as a path parameter`,
4261
});
4362
}
4463

64+
invalidSegments.sort((a, b) => a.start - b.start);
65+
4566
if (invalidSegments.length > 0) {
4667
let lastEnd = 0;
4768
const underline = invalidSegments.reduce((acc, segment) => {
@@ -52,7 +73,9 @@ export const createPathTagFunction = (pathEncoder = encodeURIPath) =>
5273
}, '');
5374

5475
throw new AnthropicError(
55-
`Path parameters result in path with invalid segments:\n${path}\n${underline}`,
76+
`Path parameters result in path with invalid segments:\n${invalidSegments
77+
.map((e) => e.error)
78+
.join('\n')}\n${path}\n${underline}`,
5679
);
5780
}
5881

0 commit comments

Comments
 (0)