Strict GraphQL query error handling

This commit is contained in:
Samuel Elliott 2022-12-14 19:36:07 +00:00
parent bdf72352fb
commit d23a65ba2e
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
5 changed files with 55 additions and 29 deletions

View File

@ -37,12 +37,25 @@ const AUTH_ERROR_CODES = {
const REPLAY_CODE_REGEX = /^[A-Z0-9]{16}$/;
export const RequestIdSymbol = Symbol('RequestId');
export const VariablesSymbol = Symbol('Variables');
export type PersistedQueryResult<T> = GraphQLSuccessResponse<T> & PersistedQueryResultData;
export interface PersistedQueryResultData {
[ResponseSymbol]: Response;
[RequestIdSymbol]: KnownRequestId;
[VariablesSymbol]: {};
}
export default class SplatNet3Api {
onTokenShouldRenew: ((remaining: number, res: Response) => Promise<SplatNet3AuthData | void>) | null = null;
onTokenExpired: ((res: Response) => Promise<SplatNet3AuthData | void>) | null = null;
/** @internal */
_renewToken: Promise<void> | null = null;
graphql_strict = process.env.NXAPI_SPLATNET3_STRICT !== '0';
protected constructor(
public bullet_token: string,
public version: string,
@ -121,7 +134,7 @@ export default class SplatNet3Api {
/** @private */
_Variables extends (V extends object ? V : _Id extends KnownRequestId ? VariablesTypes[_Id] : unknown) =
(V extends object ? V : _Id extends KnownRequestId ? VariablesTypes[_Id] : unknown),
>(id: _Id, variables: _Variables) {
>(id: _Id, variables: _Variables): Promise<PersistedQueryResult<_Result>> {
const req: GraphQLRequest<_Variables> = {
variables,
extensions: {
@ -135,12 +148,19 @@ export default class SplatNet3Api {
const data = await this.fetch<GraphQLResponse<_Result>>('/graphql', 'POST', JSON.stringify(req), undefined,
'graphql query ' + id);
if (!('data' in data)) {
throw new ErrorResponse('[splatnet3] GraphQL error: ' + data.errors.map(e => e.message).join(', '),
if (!('data' in data) || (this.graphql_strict && data.errors?.length)) {
throw new ErrorResponse('[splatnet3] GraphQL error: ' + data.errors!.map(e => e.message).join(', '),
data[ResponseSymbol], data);
}
return data;
for (const error of data.errors ?? []) {
debugGraphQl('GraphQL error in query %s: %s', id, error.message, error);
}
Object.defineProperty(data, RequestIdSymbol, {value: id});
Object.defineProperty(data, VariablesSymbol, {value: variables});
return data as PersistedQueryResult<_Result>;
}
/** * */

View File

@ -3,12 +3,12 @@ import * as fs from 'node:fs/promises';
import createDebug from 'debug';
import mkdirp from 'mkdirp';
import fetch from 'node-fetch';
import { PhotoAlbumResult, RequestId } from 'splatnet3-types/splatnet3';
import { PhotoAlbumResult } from 'splatnet3-types/splatnet3';
import type { Arguments as ParentArguments } from '../splatnet3.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getBulletToken } from '../../common/auth/splatnet3.js';
import SplatNet3Api from '../../api/splatnet3.js';
import SplatNet3Api, { RequestIdSymbol } from '../../api/splatnet3.js';
import { ResponseSymbol } from '../../api/util.js';
import { timeoutSignal } from '../../util/misc.js';
@ -67,7 +67,7 @@ export async function dumpAlbumPhotos(
debug('Writing %s', filename);
await fs.writeFile(file, JSON.stringify({
result: results.data.photoAlbum,
query: refresh ? RequestId.PhotoAlbumRefetchQuery : RequestId.PhotoAlbumQuery,
query: results[RequestIdSymbol],
app_version: splatnet.version,
be_version: results[ResponseSymbol].headers.get('x-be-version'),
}, null, 4) + '\n', 'utf-8');

View File

@ -7,7 +7,7 @@ import type { Arguments as ParentArguments } from '../splatnet3.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getBulletToken } from '../../common/auth/splatnet3.js';
import SplatNet3Api from '../../api/splatnet3.js';
import SplatNet3Api, { RequestIdSymbol } from '../../api/splatnet3.js';
import { ResponseSymbol } from '../../api/util.js';
const debug = createDebug('cli:splatnet3:dump-records');
@ -62,7 +62,7 @@ export async function dumpFestRecords(splatnet: SplatNet3Api, directory: string,
await fs.writeFile(file, JSON.stringify({
result: records.data.festRecords,
player: records.data.currentPlayer,
query: RequestId.FestRecordQuery,
query: records[RequestIdSymbol],
app_version: splatnet.version,
be_version: records[ResponseSymbol].headers.get('x-be-version'),
}, null, 4) + '\n', 'utf-8');
@ -77,7 +77,7 @@ export async function dumpFestRecords(splatnet: SplatNet3Api, directory: string,
const filename = 'splatnet3-fest-' + id + '-' +
(fest_record.state !== FestState.CLOSED ? Date.now() + '-' : '') +
RequestId.DetailFestRecordDetailQuery + '.json';
records[RequestIdSymbol] + '.json';
const file = path.join(directory, filename);
let record: Fest_detail | null = null;
@ -96,7 +96,7 @@ export async function dumpFestRecords(splatnet: SplatNet3Api, directory: string,
await fs.writeFile(file, JSON.stringify({
result: result.data.fest,
player: result.data.currentPlayer,
query: RequestId.DetailFestRecordDetailQuery,
query: result[RequestIdSymbol],
app_version: splatnet.version,
be_version: result[ResponseSymbol].headers.get('x-be-version'),
}, null, 4) + '\n', 'utf-8');
@ -121,7 +121,7 @@ export async function dumpFestRecords(splatnet: SplatNet3Api, directory: string,
debug('Writing %s', filename);
await fs.writeFile(file, JSON.stringify({
result: result.data.fest,
query: RequestId.DetailVotingStatusQuery,
query: result[RequestIdSymbol],
app_version: splatnet.version,
be_version: result[ResponseSymbol].headers.get('x-be-version'),
}, null, 4) + '\n', 'utf-8');
@ -151,7 +151,7 @@ export async function dumpFestRecords(splatnet: SplatNet3Api, directory: string,
debug('Writing %s', filename);
await fs.writeFile(file, JSON.stringify({
result: result.data.fest,
query: RequestId.DetailFestRecordDetailQuery,
query: result[RequestIdSymbol],
app_version: splatnet.version,
be_version: result[ResponseSymbol].headers.get('x-be-version'),
}, null, 4) + '\n', 'utf-8');

View File

@ -2,12 +2,11 @@ import * as path from 'node:path';
import * as fs from 'node:fs/promises';
import createDebug from 'debug';
import mkdirp from 'mkdirp';
import { RequestId } from 'splatnet3-types/splatnet3';
import type { Arguments as ParentArguments } from '../splatnet3.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getBulletToken } from '../../common/auth/splatnet3.js';
import SplatNet3Api from '../../api/splatnet3.js';
import SplatNet3Api, { RequestIdSymbol } from '../../api/splatnet3.js';
import { ResponseSymbol } from '../../api/util.js';
const debug = createDebug('cli:splatnet3:dump-records');
@ -96,7 +95,7 @@ export async function dumpHistoryRecords(splatnet: SplatNet3Api, directory: stri
await fs.writeFile(file, JSON.stringify({
result: results.data.playHistory,
player: results.data.currentPlayer,
query: refresh ? RequestId.HistoryRecordRefetchQuery : RequestId.HistoryRecordQuery,
query: results[RequestIdSymbol],
app_version: splatnet.version,
be_version: results[ResponseSymbol].headers.get('x-be-version'),
}, null, 4) + '\n', 'utf-8');
@ -114,7 +113,7 @@ export async function dumpHeroRecords(splatnet: SplatNet3Api, directory: string)
debug('Writing %s', filename);
await fs.writeFile(file, JSON.stringify({
result: results.data.heroRecord,
query: RequestId.HeroHistoryQuery,
query: results[RequestIdSymbol],
app_version: splatnet.version,
be_version: results[ResponseSymbol].headers.get('x-be-version'),
}, null, 4) + '\n', 'utf-8');
@ -134,7 +133,7 @@ export async function dumpCatalogRecords(splatnet: SplatNet3Api, directory: stri
debug('Writing %s', filename);
await fs.writeFile(file, JSON.stringify({
result: results.data.catalog,
query: refresh ? RequestId.CatalogRefetchQuery : RequestId.CatalogQuery,
query: results[RequestIdSymbol],
app_version: splatnet.version,
be_version: results[ResponseSymbol].headers.get('x-be-version'),
}, null, 4) + '\n', 'utf-8');
@ -154,7 +153,7 @@ export async function dumpStageStats(splatnet: SplatNet3Api, directory: string,
debug('Writing %s', filename);
await fs.writeFile(file, JSON.stringify({
result: results.data.stageRecords,
query: refresh ? RequestId.StageRecordsRefetchQuery : RequestId.StageRecordQuery,
query: results[RequestIdSymbol],
app_version: splatnet.version,
be_version: results[ResponseSymbol].headers.get('x-be-version'),
}, null, 4) + '\n', 'utf-8');
@ -174,7 +173,7 @@ export async function dumpWeaponStats(splatnet: SplatNet3Api, directory: string,
debug('Writing %s', filename);
await fs.writeFile(file, JSON.stringify({
result: results.data.weaponRecords,
query: refresh ? RequestId.WeaponRecordsRefetchQuery : RequestId.WeaponRecordQuery,
query: results[RequestIdSymbol],
app_version: splatnet.version,
be_version: results[ResponseSymbol].headers.get('x-be-version'),
}, null, 4) + '\n', 'utf-8');

View File

@ -7,7 +7,7 @@ import type { Arguments as ParentArguments } from '../splatnet3.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getBulletToken } from '../../common/auth/splatnet3.js';
import SplatNet3Api from '../../api/splatnet3.js';
import SplatNet3Api, { RequestIdSymbol } from '../../api/splatnet3.js';
import { ResponseSymbol } from '../../api/util.js';
import { dumpCatalogRecords, dumpHistoryRecords } from './dump-records.js';
@ -101,7 +101,7 @@ export async function dumpResults(
await fs.writeFile(file, JSON.stringify({
player: player ? {
result: player.data.currentPlayer,
query: RequestId.BattleHistoryCurrentPlayerQuery,
query: player[RequestIdSymbol],
be_version: player[ResponseSymbol].headers.get('x-be-version'),
} : undefined,
latestBattleHistories: {
@ -109,23 +109,30 @@ export async function dumpResults(
fest: battles.data.currentFest,
player: 'currentPlayer' in battles.data ?
(battles.data as LatestBattleHistoriesRefetchResult<true>).currentPlayer : undefined,
query: refresh ? RequestId.LatestBattleHistoriesRefetchQuery : RequestId.LatestBattleHistoriesQuery,
query: battles[RequestIdSymbol],
be_version: battles[ResponseSymbol].headers.get('x-be-version'),
},
regularBattleHistories: {
result: battles_regular.data.regularBattleHistories,
player: 'currentPlayer' in battles_regular.data ?
(battles_regular.data as RegularBattleHistoriesRefetchResult<true>).currentPlayer : undefined,
query: refresh ? RequestId.RegularBattleHistoriesRefetchQuery : RequestId.RegularBattleHistoriesQuery,
query: battles_regular[RequestIdSymbol],
be_version: battles_regular[ResponseSymbol].headers.get('x-be-version'),
},
bankaraBattleHistories: {
result: battles_anarchy.data.bankaraBattleHistories,
player: 'currentPlayer' in battles_anarchy.data ?
(battles_anarchy.data as BankaraBattleHistoriesRefetchResult<true>).currentPlayer : undefined,
query: refresh ? RequestId.BankaraBattleHistoriesRefetchQuery : RequestId.BankaraBattleHistoriesQuery,
query: battles_anarchy[RequestIdSymbol],
be_version: battles_anarchy[ResponseSymbol].headers.get('x-be-version'),
},
xBattleHistories: {
result: battles_xmatch.data.xBattleHistories,
player: 'currentPlayer' in battles_xmatch.data ?
(battles_xmatch.data as XBattleHistoriesRefetchResult<true>).currentPlayer : undefined,
query: battles_xmatch[RequestIdSymbol],
be_version: battles_xmatch[ResponseSymbol].headers.get('x-be-version'),
},
privateBattleHistories: {
result: battles_private.data.privateBattleHistories,
player: 'currentPlayer' in battles_private.data ?
@ -164,7 +171,7 @@ export async function dumpResults(
debug('Writing %s', filename);
await fs.writeFile(file, JSON.stringify({
result: result.data.vsHistoryDetail,
query: RequestId.VsHistoryDetailQuery,
query: result[RequestIdSymbol],
app_version: splatnet.version,
be_version: result[ResponseSymbol].headers.get('x-be-version'),
}, null, 4) + '\n', 'utf-8');
@ -201,7 +208,7 @@ export async function dumpResults(
debug('Writing %s', filename);
await fs.writeFile(file, JSON.stringify({
result: result.data.vsHistoryDetail,
query: RequestId.VsHistoryDetailQuery,
query: result[RequestIdSymbol],
app_version: splatnet.version,
be_version: result[ResponseSymbol].headers.get('x-be-version'),
}, null, 4) + '\n', 'utf-8');
@ -243,7 +250,7 @@ export async function dumpCoopResults(
debug('Writing %s', filename);
await fs.writeFile(file, JSON.stringify({
result: results.data.coopResult,
query: refresh ? RequestId.RefetchableCoopHistory_CoopResultQuery : RequestId.CoopHistoryQuery,
query: results[RequestIdSymbol],
app_version: splatnet.version,
be_version: results[ResponseSymbol].headers.get('x-be-version'),
}, null, 4) + '\n', 'utf-8');
@ -272,7 +279,7 @@ export async function dumpCoopResults(
debug('Writing %s', filename);
await fs.writeFile(file, JSON.stringify({
result: result.data.coopHistoryDetail,
query: RequestId.CoopHistoryDetailQuery,
query: result[RequestIdSymbol],
app_version: splatnet.version,
be_version: result[ResponseSymbol].headers.get('x-be-version'),
}, null, 4) + '\n', 'utf-8');