arxiv-api-wrapper 2.0.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/oaiClient.ts +40 -1
- package/tests/oai.test.ts +55 -0
package/package.json
CHANGED
package/src/oaiClient.ts
CHANGED
|
@@ -33,6 +33,7 @@ import type {
|
|
|
33
33
|
} from './oaiTypes.js';
|
|
34
34
|
|
|
35
35
|
const OAI_BASE_URL = 'https://oaipmh.arxiv.org/oai';
|
|
36
|
+
const OAI_EARLIEST_DATE = '2005-09-16';
|
|
36
37
|
|
|
37
38
|
const DEFAULT_USER_AGENT = 'arxiv-api-wrapper/1.0 (+https://export.arxiv.org)';
|
|
38
39
|
|
|
@@ -53,7 +54,7 @@ interface OaiParams {
|
|
|
53
54
|
resumptionToken?: string;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
function hasValue(value: string | undefined):
|
|
57
|
+
function hasValue(value: string | undefined): value is string {
|
|
57
58
|
return value != null && value !== '';
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -81,6 +82,40 @@ function throwResumptionTokenExclusiveError(context: 'request params' | 'list op
|
|
|
81
82
|
);
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
function parseDatePrefix(dateValue: string): string | undefined {
|
|
86
|
+
const trimmed = dateValue.trim();
|
|
87
|
+
if (!trimmed) return undefined;
|
|
88
|
+
const match = /^(\d{4}-\d{2}-\d{2})(?:$|T\d{2}:\d{2}:\d{2}Z$)/.exec(trimmed);
|
|
89
|
+
return match?.[1];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function validateFromDateNotTooEarly(from: string | undefined): void {
|
|
93
|
+
if (!hasValue(from)) return;
|
|
94
|
+
const normalizedDate = parseDatePrefix(from);
|
|
95
|
+
if (!normalizedDate) return;
|
|
96
|
+
if (normalizedDate < OAI_EARLIEST_DATE) {
|
|
97
|
+
throw new OaiError(
|
|
98
|
+
'badArgument',
|
|
99
|
+
`Invalid list options: from=${from} is earlier than arXiv's earliest supported OAI datestamp ` +
|
|
100
|
+
`(${OAI_EARLIEST_DATE}). Use from >= ${OAI_EARLIEST_DATE} or omit from.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function validateUntilDateNotTooLate(until: string | undefined): void {
|
|
106
|
+
if (!hasValue(until)) return;
|
|
107
|
+
const normalizedDate = parseDatePrefix(until);
|
|
108
|
+
if (!normalizedDate) return;
|
|
109
|
+
const todayUtc = new Date().toISOString().slice(0, 10);
|
|
110
|
+
if (normalizedDate > todayUtc) {
|
|
111
|
+
throw new OaiError(
|
|
112
|
+
'badArgument',
|
|
113
|
+
`Invalid list options: until=${until} is later than today's UTC date (${todayUtc}). ` +
|
|
114
|
+
'Use until <= today (UTC) or omit until.'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
84
119
|
/** Build OAI-PMH request URL (exported for unit tests). */
|
|
85
120
|
export function buildOaiUrl(verb: OaiVerb, params: OaiParams): string {
|
|
86
121
|
if (hasResumptionTokenConflicts(params)) {
|
|
@@ -241,6 +276,8 @@ export async function oaiListIdentifiers(
|
|
|
241
276
|
const from = listOptions?.from;
|
|
242
277
|
const until = listOptions?.until;
|
|
243
278
|
const set = listOptions?.set;
|
|
279
|
+
validateFromDateNotTooEarly(from);
|
|
280
|
+
validateUntilDateNotTooLate(until);
|
|
244
281
|
const params: OaiParams = {};
|
|
245
282
|
if (hasValue(resumptionToken)) {
|
|
246
283
|
params.resumptionToken = resumptionToken;
|
|
@@ -272,6 +309,8 @@ export async function oaiListRecords(
|
|
|
272
309
|
const from = listOptions?.from;
|
|
273
310
|
const until = listOptions?.until;
|
|
274
311
|
const set = listOptions?.set;
|
|
312
|
+
validateFromDateNotTooEarly(from);
|
|
313
|
+
validateUntilDateNotTooLate(until);
|
|
275
314
|
const params: OaiParams = {};
|
|
276
315
|
if (hasValue(resumptionToken)) {
|
|
277
316
|
params.resumptionToken = resumptionToken;
|
package/tests/oai.test.ts
CHANGED
|
@@ -294,3 +294,58 @@ describe('resumptionToken validation', () => {
|
|
|
294
294
|
});
|
|
295
295
|
});
|
|
296
296
|
});
|
|
297
|
+
|
|
298
|
+
describe('from date validation', () => {
|
|
299
|
+
it('throws a local OaiError when from is earlier than arXiv minimum date', async () => {
|
|
300
|
+
await expect(
|
|
301
|
+
oaiListRecords('oai_dc', { from: '2005-09-15' })
|
|
302
|
+
).rejects.toMatchObject({
|
|
303
|
+
name: 'OaiError',
|
|
304
|
+
code: 'badArgument',
|
|
305
|
+
});
|
|
306
|
+
await expect(oaiListRecords('oai_dc', { from: '2005-09-15' })).rejects.toThrow(
|
|
307
|
+
"earlier than arXiv's earliest supported OAI datestamp (2005-09-16)"
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('throws for earlier datetime form and allows earliest date', async () => {
|
|
312
|
+
await expect(
|
|
313
|
+
oaiListIdentifiers('oai_dc', { from: '2005-09-15T23:59:59Z' })
|
|
314
|
+
).rejects.toMatchObject({
|
|
315
|
+
name: 'OaiError',
|
|
316
|
+
code: 'badArgument',
|
|
317
|
+
});
|
|
318
|
+
const url = buildOaiUrl('ListIdentifiers', { metadataPrefix: 'oai_dc', from: '2005-09-16' });
|
|
319
|
+
expect(url).toContain('from=2005-09-16');
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
describe('until date validation', () => {
|
|
324
|
+
it('throws a local OaiError when until is in the future', async () => {
|
|
325
|
+
const tomorrowUtc = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
|
|
326
|
+
|
|
327
|
+
await expect(
|
|
328
|
+
oaiListRecords('oai_dc', { until: tomorrowUtc })
|
|
329
|
+
).rejects.toMatchObject({
|
|
330
|
+
name: 'OaiError',
|
|
331
|
+
code: 'badArgument',
|
|
332
|
+
});
|
|
333
|
+
await expect(oaiListRecords('oai_dc', { until: tomorrowUtc })).rejects.toThrow(
|
|
334
|
+
"later than today's UTC date"
|
|
335
|
+
);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('throws for future datetime form and allows today', async () => {
|
|
339
|
+
const tomorrowUtc = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
|
|
340
|
+
const todayUtc = new Date().toISOString().slice(0, 10);
|
|
341
|
+
|
|
342
|
+
await expect(
|
|
343
|
+
oaiListIdentifiers('oai_dc', { until: `${tomorrowUtc}T00:00:00Z` })
|
|
344
|
+
).rejects.toMatchObject({
|
|
345
|
+
name: 'OaiError',
|
|
346
|
+
code: 'badArgument',
|
|
347
|
+
});
|
|
348
|
+
const url = buildOaiUrl('ListIdentifiers', { metadataPrefix: 'oai_dc', until: todayUtc });
|
|
349
|
+
expect(url).toContain(`until=${todayUtc}`);
|
|
350
|
+
});
|
|
351
|
+
});
|