{"record":{"id":"7f81ec8402025335","repo":"jackwener/OpenCLI","slug":"invalid-linkedin-learning-url-s","errorCode":null,"errorMessage":"Invalid LinkedIn Learning URL: \"${s}\"","messagePattern":"Invalid LinkedIn Learning URL: \"(.+?)\"","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/linkedin-learning/course.js","lineNumber":17,"sourceCode":"/**\n * LinkedIn Learning course detail by slug, via /learning-api/courses?q=slug.\n */\nimport { cli, Strategy } from '@jackwener/opencli/registry';\nimport { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';\nimport { DOMAIN, fetchLinkedInLearningApi, normalizeWhitespace } from './shared.js';\n\nfunction parseSlug(value) {\n    const s = normalizeWhitespace(value);\n    if (!s) throw new ArgumentError('<slug> is required');\n    let slug = s;\n    if (/^https?:\\/\\//i.test(s)) {\n        let parsed;\n        try {\n            parsed = new URL(s);\n        } catch {\n            throw new ArgumentError(`Invalid LinkedIn Learning URL: \"${s}\"`);\n        }\n        const host = parsed.hostname.toLowerCase();\n        if (host !== 'linkedin.com' && host !== 'www.linkedin.com') {\n            throw new ArgumentError(`Invalid LinkedIn Learning host: \"${parsed.hostname}\"`);\n        }\n        const m = parsed.pathname.match(/^\\/learning\\/([^/?#]+)/);\n        if (!m) throw new ArgumentError(`Invalid LinkedIn Learning course URL: \"${s}\"`);\n        slug = m[1];\n    } else {\n        const m = s.match(/^\\/?learning\\/([^/?#]+)/);\n        slug = m ? m[1] : s;\n    }\n    if (!/^[a-zA-Z0-9-_]+$/.test(slug)) {\n        throw new ArgumentError(`Invalid LinkedIn Learning slug: \"${slug}\"`);\n    }\n    return slug;\n}\n","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin-learning/course.js#L1-L35","documentation":"If the slug argument looks like an HTTP(S) URL, parseSlug parses it with new URL; when the URL constructor throws (malformed URL), it throws ArgumentError with the original string embedded. It guards against syntactically invalid URLs being passed instead of plain slugs.","triggerScenarios":"Value starts with http:// or https:// but new URL(s) rejects it — e.g. 'https://', 'https://www.linkedin.com/learning/%zz' (bad percent-encoding), or a truncated paste like 'https://www.linkedin.com/learning/ course'.","commonSituations":"Truncated or whitespace-corrupted copy-paste of a course URL; shell word-splitting inserted a space into the URL; percent-encoding mangled by double-unescaping; missing scheme characters.","solutions":["Pass just the course slug (the path segment after /learning/) instead of a malformed URL.","Fix the URL: ensure it's complete and properly encoded, e.g. https://www.linkedin.com/learning/<slug>.","Quote the URL in the shell so spaces/special characters don't break it.","Decode/re-encode any odd percent sequences (%zz is invalid) before passing."],"exampleFix":"// before\nrun(['linkedin-learning', 'course', 'https://www.linkedin.com/learning/ my course']); // ArgumentError\n// after\nrun(['linkedin-learning', 'course', 'https://www.linkedin.com/learning/my-course']);\n// or just the slug:\nrun(['linkedin-learning', 'course', 'my-course']);","handlingStrategy":"validation","validationCode":"function coerceSlug(input) {\n  const s = (input ?? '').trim();\n  if (!s) throw new Error('slug required');\n  if (/^https?:\\/\\//i.test(s)) {\n    try { new URL(s); } catch { throw new Error(`not a valid URL: ${s}`); }\n    const m = new URL(s).pathname.match(/^\\/learning\\/([^/?#]+)/);\n    if (m) return m[1];\n  }\n  return s;\n}","typeGuard":"function isWellFormedUrl(s) { try { new URL(s); return true; } catch { return false; } }","tryCatchPattern":"try {\n  await runCommand(['linkedin-learning', 'course', input]);\n} catch (e) {\n  if (e.name === 'ArgumentError' && e.message.includes('Invalid LinkedIn Learning URL')) {\n    const slug = extractSlugManually(input); // fallback: strip prefix/suffix\n    return runCommand(['linkedin-learning', 'course', slug]);\n  }\n  throw e;\n}","preventionTips":["Quote URLs in the shell to prevent word-splitting artifacts.","Prefer passing the bare slug over full URLs.","Validate URLs with new URL() in a pre-step before invoking.","Watch for double-encoding issues when URLs pass through multiple tools."],"tags":["arguments","url","validation"],"backgroundTag":"invalid-url-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}