Типизирование части возвращаемых данных без 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 шт):

Автор решения: AmEl

Я много перекопал, но явного ответа не увидел. Если кто-то найдет более элегантное решение, все лавры отдам ему. А пока я предложу свой вариант: предлагается валидировать с помощью 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 };
→ Ссылка