{"record":{"id":"0738ecd3ac5f6548","repo":"jackwener/OpenCLI","slug":"z-library-book-url-must-be-a-valid-http-s-url","errorCode":null,"errorMessage":"Z-Library book URL must be a valid http(s) URL","messagePattern":"Z-Library book URL must be a valid http\\(s\\) URL","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/zlibrary/utils.js","lineNumber":20,"sourceCode":" * Z-Library adapter utilities.\n */\n\nimport { ArgumentError } from '@jackwener/opencli/errors';\n\nconst ZLIBRARY_DOMAIN = 'z-library.im';\nconst ZLIBRARY_ORIGIN = `https://${ZLIBRARY_DOMAIN}`;\nconst ZLIBRARY_ALLOWED_HOSTS = new Set([\n  ZLIBRARY_DOMAIN,\n  `www.${ZLIBRARY_DOMAIN}`,\n]);\n\nexport function normalizeZlibraryBookUrl(input) {\n  const raw = String(input || '').trim();\n  let url;\n  try {\n    url = new URL(raw);\n  } catch {\n    throw new ArgumentError('Z-Library book URL must be a valid http(s) URL', `Example: ${ZLIBRARY_ORIGIN}/book/...`);\n  }\n  if (!['http:', 'https:'].includes(url.protocol) || !ZLIBRARY_ALLOWED_HOSTS.has(url.hostname)) {\n    throw new ArgumentError(\n      `Unsupported Z-Library URL host: ${url.hostname}`,\n      `Pass a book URL under ${ZLIBRARY_DOMAIN}, for example ${ZLIBRARY_ORIGIN}/book/...`,\n    );\n  }\n  return url.toString();\n}\n\n/**\n * Build a Z-Library search URL.\n * Z-Library uses /s/<url-encoded-query> for search.\n */\nexport function buildSearchUrl(query) {\n  const normalized = String(query || '').trim();\n  if (!normalized) {\n    throw new ArgumentError('zlibrary search query cannot be empty');","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/zlibrary/utils.js#L2-L38","documentation":"normalizeZlibraryBookUrl throws ArgumentError (code ARGUMENT, exit 2) when its input cannot be parsed as a URL at all via new URL(). The library validates the book URL argument up front so navigation never starts with a malformed address.","triggerScenarios":"Calling a zlibrary info/download command with a missing, empty, or malformed book URL (e.g. 'my book', 'zlibrary/book/123', a bare ID without scheme).","commonSituations":"Quoting mistakes in shell so the URL gets split/truncated, pasting a relative path instead of a full URL, or passing a placeholder that was never substituted in a script.","solutions":["Pass the complete URL including scheme, e.g. https://<zlibrary-domain>/book/12345","Quote the URL in the shell so special characters are preserved","Check for unexpanded shell/script variables in the argument","Resolve the book URL via `zlibrary search` first, then use the returned link"],"exampleFix":"// before\nawait zlibraryInfo({ url: 'zlibrary/book/12345' });\n// after\nawait zlibraryInfo({ url: 'https://z-library.example/book/12345' });","handlingStrategy":"validation","validationCode":"function isHttpUrl(s) {\n  try { const u = new URL(String(s)); return u.protocol === 'http:' || u.protocol === 'https:'; }\n  catch { return false; }\n}\nif (!isHttpUrl(bookUrl)) throw new Error(`invalid book URL: ${bookUrl}`);","typeGuard":"function isBookUrl(v) {\n  if (typeof v !== 'string') return false;\n  try { const u = new URL(v); return ['http:', 'https:'].includes(u.protocol); }\n  catch { return false; }\n}","tryCatchPattern":"try {\n  await zlibraryInfo({ url: bookUrl });\n} catch (e) {\n  if (e?.code === 'ARGUMENT') { /* fix the URL argument */ }\n  throw e;\n}","preventionTips":["Always pass fully-qualified http(s) URLs","Quote URLs in shell commands","Resolve URLs via `zlibrary search` rather than hand-typing"],"tags":["argument-validation","url"],"backgroundTag":"invalid-url-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}