{"record":{"id":"0f1fad55681d9d6f","repo":"tinyhumansai/openhuman","slug":"provider-is-required","errorCode":null,"errorMessage":"provider is required","messagePattern":"provider is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/api/rest.rs","lineNumber":452,"sourceCode":"    /// drive a non-JSON request shape (e.g. `multipart/form-data` uploads\n    /// for cloud STT) without re-implementing TLS/proxy plumbing.\n    pub fn raw_client(&self) -> &Client {\n        &self.client\n    }\n\n    /// Resolve a backend-relative path against the configured base URL.\n    /// Mirrors what `authed_json` does internally so callers using\n    /// `raw_client()` don't have to assemble URLs by hand.\n    pub fn url_for(&self, path: &str) -> Result<Url> {\n        self.base\n            .join(path.trim_start_matches('/'))\n            .with_context(|| format!(\"build URL for {path}\"))\n    }\n\n    /// Returns the URL for initiating a login flow for a specific provider.\n    pub fn login_url(&self, provider: &str) -> Result<Url> {\n        let p = provider.trim().trim_matches('/');\n        anyhow::ensure!(!p.is_empty(), \"provider is required\");\n        self.base\n            .join(&format!(\"auth/{p}/login\"))\n            .context(\"build login URL\")\n    }\n\n    /// Initiates an OAuth connection flow for the current user and a specific provider.\n    pub async fn connect(\n        &self,\n        provider: &str,\n        bearer_jwt: &str,\n        skill_id: Option<&str>,\n        response_type: Option<&str>,\n        encryption_mode: Option<&str>,\n    ) -> Result<ConnectResponse> {\n        let p = provider.trim().trim_matches('/');\n        anyhow::ensure!(!p.is_empty(), \"provider is required\");\n        let query = {\n            let mut serializer = url::form_urlencoded::Serializer::new(String::new());","sourceCodeStart":434,"sourceCodeEnd":470,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/src/api/rest.rs#L434-L470","documentation":"Thrown by BackendOAuthClient::login_url when the provider argument is empty after trimming whitespace and stripping leading/trailing slashes. The provider string becomes a path segment (\"auth/{provider}/login\"), so an empty value would build a malformed login URL; the guard fails fast instead. It is a pure caller-input check — no network is touched.","triggerScenarios":"Calling login_url(\"\"), login_url(\"   \"), or login_url(\"/\") — e.g. the provider slug came from a config field, CLI flag, or UI selection that was never filled in.","commonSituations":"A provider dropdown in the UI submitted with no selection, a config template shipping an empty [[provider]] entry, or a default provider constant that was renamed to an empty string.","solutions":["Pass a concrete provider id such as \"telegram\", \"google\", \"discord\"","Validate the provider selection at the UI/CLI boundary before invoking login_url","Default to a known provider when the field is optional in your flow"],"exampleFix":"// before\nlet url = client.login_url(&selected_provider)?; // selected_provider = \"\"\n\n// after\nlet provider = selected_provider.trim().trim_matches('/');\nanyhow::ensure!(!provider.is_empty(), \"select a provider first\");\nlet url = client.login_url(provider)?;","handlingStrategy":"validation","validationCode":"let provider = provider.trim().trim_matches('/');\nanyhow::ensure!(!provider.is_empty(), \"provider must be selected before building login URL\");\nlet url = client.login_url(provider)?;","typeGuard":"fn is_provider_slug(s: &str) -> bool {\n    let t = s.trim().trim_matches('/');\n    !t.is_empty() && t.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')\n}","tryCatchPattern":null,"preventionTips":["Make provider an enum or a validated string at the UI/CLI boundary, not a free text field","Default the dropdown to a concrete provider so the unselected state never reaches the call"],"tags":["rust","validation","oauth","provider"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}