Skip to content

Commit a9bffe7

Browse files
author
Lubos ​
committed
chore: update custom local client
1 parent b43e917 commit a9bffe7

File tree

15 files changed

+576
-35
lines changed

15 files changed

+576
-35
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export type AuthToken = string | undefined;
2+
3+
export interface Auth {
4+
/**
5+
* Which part of the request do we use to send the auth?
6+
*
7+
* @default 'header'
8+
*/
9+
in?: 'header' | 'query' | 'cookie';
10+
/**
11+
* Header or query parameter name.
12+
*
13+
* @default 'Authorization'
14+
*/
15+
name?: string;
16+
scheme?: 'basic' | 'bearer';
17+
type: 'apiKey' | 'http';
18+
}
19+
20+
export const getAuthToken = async (
21+
auth: Auth,
22+
callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,
23+
): Promise<string | undefined> => {
24+
const token =
25+
typeof callback === 'function' ? await callback(auth) : callback;
26+
27+
if (!token) {
28+
return;
29+
}
30+
31+
if (auth.scheme === 'bearer') {
32+
return `Bearer ${token}`;
33+
}
34+
35+
if (auth.scheme === 'basic') {
36+
return `Basic ${btoa(token)}`;
37+
}
38+
39+
return token;
40+
};
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import type {
2+
ArrayStyle,
3+
ObjectStyle,
4+
SerializerOptions,
5+
} from './pathSerializer';
6+
7+
export type QuerySerializer = (query: Record<string, unknown>) => string;
8+
9+
export type BodySerializer = (body: any) => any;
10+
11+
export interface QuerySerializerOptions {
12+
allowReserved?: boolean;
13+
array?: SerializerOptions<ArrayStyle>;
14+
object?: SerializerOptions<ObjectStyle>;
15+
}
16+
17+
const serializeFormDataPair = (data: FormData, key: string, value: unknown) => {
18+
if (typeof value === 'string' || value instanceof Blob) {
19+
data.append(key, value);
20+
} else {
21+
data.append(key, JSON.stringify(value));
22+
}
23+
};
24+
25+
const serializeUrlSearchParamsPair = (
26+
data: URLSearchParams,
27+
key: string,
28+
value: unknown,
29+
) => {
30+
if (typeof value === 'string') {
31+
data.append(key, value);
32+
} else {
33+
data.append(key, JSON.stringify(value));
34+
}
35+
};
36+
37+
export const formDataBodySerializer = {
38+
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
39+
body: T,
40+
) => {
41+
const data = new FormData();
42+
43+
Object.entries(body).forEach(([key, value]) => {
44+
if (value === undefined || value === null) {
45+
return;
46+
}
47+
if (Array.isArray(value)) {
48+
value.forEach((v) => serializeFormDataPair(data, key, v));
49+
} else {
50+
serializeFormDataPair(data, key, value);
51+
}
52+
});
53+
54+
return data;
55+
},
56+
};
57+
58+
export const jsonBodySerializer = {
59+
bodySerializer: <T>(body: T) =>
60+
JSON.stringify(body, (key, value) =>
61+
typeof value === 'bigint' ? value.toString() : value,
62+
),
63+
};
64+
65+
export const urlSearchParamsBodySerializer = {
66+
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
67+
body: T,
68+
) => {
69+
const data = new URLSearchParams();
70+
71+
Object.entries(body).forEach(([key, value]) => {
72+
if (value === undefined || value === null) {
73+
return;
74+
}
75+
if (Array.isArray(value)) {
76+
value.forEach((v) => serializeUrlSearchParamsPair(data, key, v));
77+
} else {
78+
serializeUrlSearchParamsPair(data, key, value);
79+
}
80+
});
81+
82+
return data.toString();
83+
},
84+
};
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
type Slot = 'body' | 'headers' | 'path' | 'query';
2+
3+
export type Field =
4+
| {
5+
in: Exclude<Slot, 'body'>;
6+
key: string;
7+
map?: string;
8+
}
9+
| {
10+
in: Extract<Slot, 'body'>;
11+
key?: string;
12+
map?: string;
13+
};
14+
15+
export interface Fields {
16+
allowExtra?: Partial<Record<Slot, boolean>>;
17+
args?: ReadonlyArray<Field>;
18+
}
19+
20+
export type FieldsConfig = ReadonlyArray<Field | Fields>;
21+
22+
const extraPrefixesMap: Record<string, Slot> = {
23+
$body_: 'body',
24+
$headers_: 'headers',
25+
$path_: 'path',
26+
$query_: 'query',
27+
};
28+
const extraPrefixes = Object.entries(extraPrefixesMap);
29+
30+
type KeyMap = Map<
31+
string,
32+
{
33+
in: Slot;
34+
map?: string;
35+
}
36+
>;
37+
38+
const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
39+
if (!map) {
40+
map = new Map();
41+
}
42+
43+
for (const config of fields) {
44+
if ('in' in config) {
45+
if (config.key) {
46+
map.set(config.key, {
47+
in: config.in,
48+
map: config.map,
49+
});
50+
}
51+
} else if (config.args) {
52+
buildKeyMap(config.args, map);
53+
}
54+
}
55+
56+
return map;
57+
};
58+
59+
interface Params {
60+
body: unknown;
61+
headers: Record<string, unknown>;
62+
path: Record<string, unknown>;
63+
query: Record<string, unknown>;
64+
}
65+
66+
const stripEmptySlots = (params: Params) => {
67+
for (const [slot, value] of Object.entries(params)) {
68+
if (value && typeof value === 'object' && !Object.keys(value).length) {
69+
delete params[slot as Slot];
70+
}
71+
}
72+
};
73+
74+
export const buildClientParams = (
75+
args: ReadonlyArray<unknown>,
76+
fields: FieldsConfig,
77+
) => {
78+
const params: Params = {
79+
body: {},
80+
headers: {},
81+
path: {},
82+
query: {},
83+
};
84+
85+
const map = buildKeyMap(fields);
86+
87+
let config: FieldsConfig[number] | undefined;
88+
89+
for (const [index, arg] of args.entries()) {
90+
if (fields[index]) {
91+
config = fields[index];
92+
}
93+
94+
if (!config) {
95+
continue;
96+
}
97+
98+
if ('in' in config) {
99+
if (config.key) {
100+
const field = map.get(config.key)!;
101+
const name = field.map || config.key;
102+
(params[field.in] as Record<string, unknown>)[name] = arg;
103+
} else {
104+
params.body = arg;
105+
}
106+
} else {
107+
for (const [key, value] of Object.entries(arg ?? {})) {
108+
const field = map.get(key);
109+
110+
if (field) {
111+
const name = field.map || key;
112+
(params[field.in] as Record<string, unknown>)[name] = value;
113+
} else {
114+
const extra = extraPrefixes.find(([prefix]) =>
115+
key.startsWith(prefix),
116+
);
117+
118+
if (extra) {
119+
const [prefix, slot] = extra;
120+
(params[slot] as Record<string, unknown>)[
121+
key.slice(prefix.length)
122+
] = value;
123+
} else {
124+
for (const [slot, allowed] of Object.entries(
125+
config.allowExtra ?? {},
126+
)) {
127+
if (allowed) {
128+
(params[slot as Slot] as Record<string, unknown>)[key] = value;
129+
break;
130+
}
131+
}
132+
}
133+
}
134+
}
135+
}
136+
}
137+
138+
stripEmptySlots(params);
139+
140+
return params;
141+
};

0 commit comments

Comments
 (0)