{"record":{"id":"3229aed812de7992","repo":"jackwener/OpenCLI","slug":"designer-must-be-a-dribbble-username-or-profile-sl","errorCode":null,"errorMessage":"designer must be a Dribbble username or profile slug","messagePattern":"designer must be a Dribbble username or profile slug","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/dribbble/utils.js","lineNumber":43,"sourceCode":"\nexport function requireQuery(value, label = 'query') {\n    const query = String(value ?? '').trim();\n    if (!query) throw new ArgumentError(`${label} is required`);\n    if (query.length > 100) throw new ArgumentError(`${label} must be <= 100 characters`);\n    return query;\n}\n\nexport function optionalQuery(value, label = 'query') {\n    const query = String(value ?? '').trim();\n    if (query.length > 100) throw new ArgumentError(`${label} must be <= 100 characters`);\n    return query;\n}\n\nexport function requireDesigner(value) {\n    const designer = String(value ?? '').trim();\n    if (!designer) throw new ArgumentError('designer is required (for example: halolab)');\n    if (!/^[A-Za-z0-9_-]{1,100}$/.test(designer)) {\n        throw new ArgumentError('designer must be a Dribbble username or profile slug');\n    }\n    return designer;\n}\n\nexport function requireShotTarget(value) {\n    const target = String(value ?? '').trim();\n    if (!target) throw new ArgumentError('shot is required (numeric id or dribbble.com/shots URL)');\n    if (/^\\d+$/.test(target)) return target;\n\n    let url;\n    try {\n        url = new URL(target);\n    } catch {\n        throw new ArgumentError('shot must be a numeric id or dribbble.com/shots URL');\n    }\n    if (!/(^|\\.)dribbble\\.com$/i.test(url.hostname)) {\n        throw new ArgumentError('shot URL must use dribbble.com');\n    }","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/dribbble/utils.js#L25-L61","documentation":"requireDesigner enforces the Dribbble username/slug format: 1-100 characters of letters, digits, underscore, or hyphen. This ArgumentError is thrown when the supplied designer value is non-empty but contains characters outside that set, so it cannot be a valid profile slug.","triggerScenarios":"Passing a full profile URL (e.g. 'https://dribbble.com/halolab'), a value with spaces or '@' (e.g. '@halolab', 'halo lab'), unicode names, or names longer than 100 characters into a designer-based command.","commonSituations":"Copy-pasting the whole Dribbble profile URL instead of the slug, prefixing the handle with '@', or collecting the designer name from free-form user input without sanitizing it.","solutions":["Strip the value down to the slug: take the last path segment of a profile URL and remove any leading '@'.","Validate locally with /^[A-Za-z0-9_-]{1,100}$/ before calling the library.","Reject or normalize input containing spaces, slashes, or non-ASCII characters at the point of collection."],"exampleFix":"// before\ncli.shots({ designer: 'https://dribbble.com/halolab' })\n\n// after\nconst designer = 'https://dribbble.com/halolab'.split('/').pop().replace(/^@/, '');\ncli.shots({ designer }); // 'halolab'","handlingStrategy":"validation","validationCode":"function normalizeDesigner(input) {\n  if (typeof input !== 'string') throw new Error('designer must be a string');\n  const slug = input.trim().replace(/^@/, '').split('/').pop();\n  if (!/^[A-Za-z0-9_-]{1,100}$/.test(slug)) throw new Error(`invalid designer slug: ${input}`);\n  return slug;\n}","typeGuard":"function isDesignerSlug(v) {\n  return typeof v === 'string' && v.trim().length > 0 && v.trim().length <= 100 && /^[A-Za-z0-9_-]+$/.test(v.trim());\n}","tryCatchPattern":"try {\n  const rows = command({ designer });\n} catch (e) {\n  if (e instanceof ArgumentError && /Dribbble username or profile slug/.test(e.message)) {\n    console.error(`'${designer}' is not a valid slug; use only letters, digits, '_' or '-'`);\n    return;\n  }\n  throw e;\n}","preventionTips":["Normalize URLs and @handles to bare slugs at the boundary where input is collected.","Reuse the same /^[A-Za-z0-9_-]{1,100}$/ regex in your own validation so errors surface earlier.","Document in team docs that designer input is a slug, never a full URL."],"tags":["argument-validation","format-validation","regex"],"backgroundTag":"invalid-argument-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}