{"record":{"id":"3bae92783524e104","repo":"jackwener/OpenCLI","slug":"openalex-label-must-be-a-positive-integer","errorCode":null,"errorMessage":"openalex ${label} must be a positive integer","messagePattern":"openalex (.+?) must be a positive integer","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/openalex/utils.js","lineNumber":30,"sourceCode":"\n// OpenAlex stable IDs: a single-letter prefix (`W` works, `A` authors, `S`\n// sources, `I` institutions…) + at least 4 digits. We accept just `W` here.\nconst WORK_ID = /^W\\d{4,}$/;\n// DOIs are loose — accept anything starting with \"10.\" after the optional\n// `doi.org/` prefix; OpenAlex itself does the normalization.\nconst DOI_BARE = /^10\\.\\S+$/;\n\nexport function requireString(value, label) {\n    const s = String(value ?? '').trim();\n    if (!s) throw new ArgumentError(`openalex ${label} cannot be empty`);\n    return s;\n}\n\nexport function requireBoundedInt(value, defaultValue, maxValue, label = 'limit') {\n    const raw = value ?? defaultValue;\n    const n = typeof raw === 'number' ? raw : Number(raw);\n    if (!Number.isInteger(n) || n <= 0) {\n        throw new ArgumentError(`openalex ${label} must be a positive integer`);\n    }\n    if (n > maxValue) {\n        throw new ArgumentError(`openalex ${label} must be <= ${maxValue}`);\n    }\n    return n;\n}\n\n/**\n * Resolve a user-supplied work identifier to OpenAlex's canonical path\n * segment. Accepts `W…` IDs, `doi:10.…`, raw DOIs, or full\n * `https://doi.org/…` / `https://openalex.org/W…` URLs.\n */\nexport function requireWorkRef(value) {\n    const raw = String(value ?? '').trim();\n    if (!raw) {\n        throw new ArgumentError('openalex work id is required (e.g. \"W2741809807\", \"10.7717/peerj.4375\")');\n    }\n    // 1) full openalex URL","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/openalex/utils.js#L12-L48","documentation":"requireBoundedInt validates that a numeric argument (default label 'limit') is a positive integer and throws ArgumentError when it is zero, negative, fractional, or non-numeric. This guards the OpenAlex `per-page` parameter, which requires an integer >= 1. Non-integer numeric strings like '3.5' or 'abc' also fail because Number() coercion yields a non-integer or NaN.","triggerScenarios":"Calling a command with `limit` set to 0, a negative number, a decimal like 2.5, or a non-numeric string such as 'ten' or '10abc' (Number('10abc') is NaN).","commonSituations":"Passing `--limit 0` expecting 'unlimited'; copy-pasting a value with a trailing character; a script emitting 'NaN' or 'undefined' into the flag; misunderstanding that limit is 1-based and required.","solutions":["Pass a whole number >= 1 for limit (or omit it to use the default)","Coerce/validate the value with Number.isInteger before calling","Check the upstream variable isn't 0, NaN, or a string with stray characters"],"exampleFix":"// before\nawait search({ query, limit: 0 });\n// after\nawait search({ query, limit: 25 });","handlingStrategy":"validation","validationCode":"function parseLimit(raw, max = 200) {\n  const n = typeof raw === 'number' ? raw : Number(raw);\n  if (!Number.isInteger(n) || n <= 0) throw new Error(`limit must be a positive integer, got ${raw}`);\n  return Math.min(n, max);\n}","typeGuard":"function isPositiveInt(v) {\n  return typeof v === 'number' && Number.isInteger(v) && v > 0;\n}","tryCatchPattern":"try {\n  await search({ query, limit });\n} catch (e) {\n  if (e.name === 'ArgumentError' && e.message.includes('positive integer')) {\n    console.error('limit must be a whole number >= 1'); process.exitCode = 2;\n  } else throw e;\n}","preventionTips":["Coerce CLI flags with Number() and check Number.isInteger before calling","Never pass 0 expecting 'unlimited' — omit limit to use the default","Watch for 'NaN' leaking from upstream variables"],"tags":["openalex","argument-validation","numeric-range"],"backgroundTag":"invalid-argument-value","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}