{"record":{"id":"c0c8a129cc0e4aaa","repo":"nikivdev/code","slug":"host-must-end-with-localhost","errorCode":null,"errorMessage":"Host must end with .localhost","messagePattern":"Host must end with \\.localhost","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/domains.rs","lineNumber":284,"sourceCode":"    Ok(())\n}\n\nfn normalize_host(raw: &str) -> Result<String> {\n    let mut host = raw.trim().to_ascii_lowercase();\n    if let Some(stripped) = host.strip_prefix(\"http://\") {\n        host = stripped.to_string();\n    } else if let Some(stripped) = host.strip_prefix(\"https://\") {\n        host = stripped.to_string();\n    }\n    host = host.trim_end_matches('/').to_string();\n    if host.is_empty() {\n        bail!(\"Host is empty\");\n    }\n    if host.contains('/') || host.contains(':') || host.contains(char::is_whitespace) {\n        bail!(\"Host must be a hostname like linsa.localhost\");\n    }\n    if !host.ends_with(\".localhost\") {\n        bail!(\"Host must end with .localhost\");\n    }\n    if host == \".localhost\" || host == \"localhost\" {\n        bail!(\"Host must include a subdomain (for example: linsa.localhost)\");\n    }\n    Ok(host)\n}\n\nfn normalize_target(raw: &str) -> Result<String> {\n    let mut target = raw.trim().to_string();\n    if let Some(stripped) = target.strip_prefix(\"http://\") {\n        target = stripped.to_string();\n    } else if let Some(stripped) = target.strip_prefix(\"https://\") {\n        target = stripped.to_string();\n    }\n    target = target.trim_end_matches('/').to_string();\n    if target.is_empty() {\n        bail!(\"Target is empty\");\n    }","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/nikivdev/code/blob/a747e741ae92c09071d0ae946ab48488adcff1ce/src/domains.rs#L266-L302","documentation":"All domain routes live under the .localhost TLD so the local proxy can resolve them. After other checks pass, normalize_host rejects any host that does not end with \".localhost\".","triggerScenarios":"Adding/getting/removing a route with hosts like \"linsa.dev\", \"myapp.com\", or \"linsa.local\".","commonSituations":"Typing a real production domain by habit, using the older \".local\" dev TLD, or forgetting this tool's .localhost convention.","solutions":["Use a <subdomain>.localhost host, e.g. linsa.localhost","Adjust any scripts/configs that pass non-.localhost hosts","Configure your browser/resolver to use .localhost for local dev"],"exampleFix":"// before\nf domains add linsa.dev :3000\n// after\nf domains add linsa.localhost :3000","handlingStrategy":"validation","validationCode":"let h = raw.trim();\nif !h.ends_with(\".localhost\") {\n    return Err(format!(\"{h} must end with .localhost\"));\n}","typeGuard":"fn is_localhost_host(h: &str) -> bool {\n    h.ends_with(\".localhost\") && h != \".localhost\" && h != \"localhost\"\n}","tryCatchPattern":null,"preventionTips":["Standardize on <project>.localhost names for local dev","Migrate any legacy .local hosts to .localhost","Add the .localhost check to your own input parsing"],"tags":["validation","domains","naming-convention"],"backgroundTag":"invalid-input","analyzedSha":"a747e741ae92c09071d0ae946ab48488adcff1ce","analyzedAt":"2026-09-01T22:43:55.719Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}