diff --git a/src/backend/platforms/metaculus/api.ts b/src/backend/platforms/metaculus/api.ts index ad950e5..760b6b0 100644 --- a/src/backend/platforms/metaculus/api.ts +++ b/src/backend/platforms/metaculus/api.ts @@ -88,7 +88,7 @@ const optionalPageProps = { }, } as const; -const apiQuestionSchema = { +const questionSchema = { discriminator: "type", mapping: { forecast: { @@ -141,10 +141,19 @@ const apiQuestionSchema = { }, } as const; -const apiMultipleQuestionsSchema = { +const knownQuestionTypes = Object.keys(questionSchema.mapping); + +const shallowMultipleQuestionsSchema = { properties: { results: { - elements: apiQuestionSchema, + elements: { + properties: { + type: { + type: "string", + }, + }, + additionalProperties: true, + }, }, next: { type: "string", @@ -160,15 +169,22 @@ export type ApiCommon = JTDDataType<{ export type ApiPredictable = JTDDataType<{ properties: typeof predictableProps; }>; -export type ApiQuestion = JTDDataType; -export type ApiMultipleQuestions = JTDDataType< - typeof apiMultipleQuestionsSchema +export type ApiQuestion = JTDDataType; + +type ApiShallowMultipleQuestions = JTDDataType< + typeof shallowMultipleQuestionsSchema >; -const validateApiQuestion = new Ajv().compile(apiQuestionSchema); -const validateApiMultipleQuestions = new Ajv().compile( - apiMultipleQuestionsSchema -); +export type ApiMultipleQuestions = { + results: ApiQuestion[]; + next: ApiShallowMultipleQuestions["next"]; // Omit doesn't work correctly here +}; + +const validateQuestion = new Ajv().compile(questionSchema); +const validateShallowMultipleQuestions = + new Ajv().compile( + shallowMultipleQuestionsSchema + ); async function fetchWithRetries(url: string): Promise { try { @@ -209,12 +225,35 @@ const fetchAndValidate = async ( export async function fetchApiQuestions( next: string ): Promise { - return await fetchAndValidate(next, validateApiMultipleQuestions); + const data = await fetchAndValidate(next, validateShallowMultipleQuestions); + + const isDefined = (argument: T | undefined): argument is T => { + return argument !== undefined; + }; + + return { + ...data, + results: data.results + .map((result) => { + if (!knownQuestionTypes.includes(result.type)) { + console.warn(`Unknown result type ${result.type}, skipping`); + return undefined; + } + if (!validateQuestion(result)) { + throw new Error( + `Response validation failed: ` + + JSON.stringify(validateQuestion.errors) + ); + } + return result; + }) + .filter(isDefined), + }; } export async function fetchSingleApiQuestion(id: number): Promise { return await fetchAndValidate( `https://www.metaculus.com/api2/questions/${id}/`, - validateApiQuestion + validateQuestion ); }