Типизирование части возвращаемых данных без as
У меня есть недо-эндпоинт, который шлет однотипные запросы для получения входных/выходных файлов у тесткейсов (похожий пример можно найти в документации у RTKQ):
const buildTaskFileRequest = (taskId: string, filename: string) => ({
url: `/task/${encodeURIComponent(taskId)}/${encodeURIComponent(filename)}`,
responseHandler: "text" as const,
});
...
getTaskTests: builder.query<TestCase[], Task>({
async queryFn({ id, tests }, _api, _extra, baseQuery) {
try {
const results = await Promise.all(
tests.map(async ({ order, inputFile, outputFile }) => {
const [input, output] = await Promise.all([
baseQuery(buildTaskFileRequest(id, inputFile)),
baseQuery(buildTaskFileRequest(id, outputFile)),
]);
// FetchBaseQueryError
if (input.error) throw input.error;
if (output.error) throw output.error;
return {
order: order,
input: input.data as string,
output: output.data as string,
};
}),
);
return { data: results.sort((a, b) => a.order - b.order) };
} catch (err) {
// Тут тоже type asserion, но в целом в доках так делают, так что более спокоен здесь
return { error: err as FetchBaseQueryError };
}
},
}),
Когда я отправляю запросы через baseQuery, мне в ответ приходит следующий промис: MaybePromise<QueryReturnValue<unknown, FetchBaseQueryError, FetchBaseQueryMeta>>. И вот как раз кусок, отвечающий за Data в троице у QueryReturnValue, меня и напрягает - он unknown.
Никакого интерфейса baseQuery не предоставляет, чтобы я просто сказал ему baseQuery<string>, и приходится преобразовывать типы через as, чего делать не особо хочется. Есть ли менее болезненные пути? Заранее спасибо.
Ответы (1 шт):
Я много перекопал, но явного ответа не увидел. Если кто-то найдет более элегантное решение, все лавры отдам ему. А пока я предложу свой вариант: предлагается валидировать с помощью Superstruct.
Определим структуру для TestCase:
export const TestCaseStruct = object({
order: number(),
input: string(),
output: string(),
});
Чтобы не писать один и тот же код, воспользуемся утилитарным типом из Superstruct'а:
export type TestCase = Infer<typeof TestCaseStruct>;
Затем, в самом запросе, создадим соответствующий объект при помощи create (наверное, можно создать объект, а потом прогнать assert'ом, но мне так больше нравится), где укажем наш тип структуры и сообщение при ошибке:
const testCase: TestCase = create({
order,
input: input.data,
output: output.data,
}, TestCaseStruct, `Unable to parse test case #${order}`);
Теперь, помимо FetchBaseQueryError, может и возникнуть StructError от Superstruct'а. Проверим ее тоже:
if (err instanceof StructError) {
return { error: { status: 400, data: err.message } };
}
return { error: err as FetchBaseQueryError };