{"record":{"id":"76060f8b4a8a762b","repo":"tonhowtf/omniget","slug":"escolha-a-pasta-de-destino-substack","errorCode":null,"errorMessage":"escolha a pasta de destino","messagePattern":"escolha a pasta de destino","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/tools/blogs/substack.rs","lineNumber":387,"sourceCode":"            \"sem a sessão do Substack não dá para listar as suas assinaturas. Capture os cookies de substack.com no gerenciador, ou digite as publicações à mão\"\n        ));\n    }\n    let v = fetcher\n        .get_json(\"https://substack.com/api/v1/subscriptions\")\n        .await?;\n    let subs = parse_subscriptions(&v);\n    if subs.is_empty() {\n        return Err(anyhow!(\n            \"a conta logada não tem nenhuma assinatura ativa (ou a sessão expirou)\"\n        ));\n    }\n    Ok(subs)\n}\n\npub async fn run(opts: &Options, progress: ProgressFn) -> Result<ArchiveResult> {\n    let dest = opts.dest.trim();\n    if dest.is_empty() {\n        return Err(anyhow!(\"escolha a pasta de destino\"));\n    }\n    if !opts.markdown && !opts.html && !opts.json {\n        return Err(anyhow!(\"escolha ao menos um formato\"));\n    }\n    let root = PathBuf::from(dest);\n    std::fs::create_dir_all(&root)?;\n\n    let domains: Vec<String> = COOKIE_DOMAINS.iter().map(|d| d.to_string()).collect();\n    let fetcher = Fetcher::new(opts.delay_ms, opts.session_netscape.as_deref(), &domains)?;\n\n    // Lista de publicações: o que o usuário digitou tem precedência; vazio\n    // significa \"as minhas assinaturas\".\n    let targets: Vec<Subscription> = if opts.publications.is_empty() {\n        crate::core::tools::report(\n            &progress,\n            ID,\n            \"discover\",\n            0,","sourceCodeStart":369,"sourceCodeEnd":405,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/tools/blogs/substack.rs#L369-L405","documentation":"substack::run validates Options before exporting. If opts.dest, after trimming, is an empty string there is no directory to write the archive into, so it fails fast with 'escolha a pasta de destino' (choose the destination folder).","triggerScenarios":"Calling run(&opts, progress) with opts.dest set to \"\", whitespace-only, or simply never assigned by the caller/UI.","commonSituations":"Frontend did not bind the folder-picker result into the options struct; user clicked export before selecting a folder; dest field cleared programmatically.","solutions":["Set opts.dest to a valid existing-or-creatable directory path before calling run","In the UI, disable the export action until a destination folder is chosen","Trim and validate dest in the caller (e.g. show a folder picker) before invoking run"],"exampleFix":"// before\nlet opts = Options { dest: \"\".into(), ..Default::default() };\nsubstack::run(&opts, progress).await?;\n// after\nlet opts = Options { dest: \"/home/user/exports/substack\".into(), ..Default::default() };\nsubstack::run(&opts, progress).await?;","handlingStrategy":"validation","validationCode":"let dest = opts.dest.trim();\nif dest.is_empty() {\n    anyhow::bail!(\"destination folder required\");\n}","typeGuard":"fn has_dest(opts: &Options) -> bool { !opts.dest.trim().is_empty() }","tryCatchPattern":null,"preventionTips":["Validate all Options fields at construction time, before network work starts","In UIs, keep the export button disabled until dest is set","Use a default destination directory when the user has not chosen one"],"tags":["validation","substack","rust","missing-argument"],"backgroundTag":"empty-required-field","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}