#[cfg(feature = "auth_code")]
mod example {
use anyhow::{Context, Result};
use force::api::RestOperation;
use force::auth::{AuthorizationCode, AuthorizeUrlBuilder, PkceChallenge};
use force::client::ForceClientBuilder;
use force::types::QueryResult;
fn required_env(name: &str) -> Result<String> {
std::env::var(name).with_context(|| format!("{name} environment variable not set"))
}
pub async fn run() -> Result<()> {
let client_id = required_env("SF_CLIENT_ID")?;
let redirect_uri = required_env("SF_REDIRECT_URI")?;
let client_secret = std::env::var("SF_CLIENT_SECRET").ok();
let login_url = std::env::var("SF_LOGIN_URL")
.unwrap_or_else(|_| "https://login.salesforce.com".to_string());
let token_url = format!("{}/services/oauth2/token", login_url.trim_end_matches('/'));
let pkce = PkceChallenge::generate();
let authorize_url = AuthorizeUrlBuilder::new(&login_url, &client_id, &redirect_uri, &pkce)
.scope("api")
.scope("refresh_token")
.state("example-state-token")
.build();
println!("=== Stage 1: Authorize ===");
println!("Open this URL in your browser and log in:\n");
println!(" {authorize_url}\n");
println!(
"After approving, Salesforce redirects to your redirect_uri with a\n\
`?code=...` parameter. Copy that code value.\n"
);
let code = match std::env::var("SF_AUTH_CODE") {
Ok(code) if !code.trim().is_empty() => code.trim().to_string(),
_ => {
println!("Paste the authorization code and press Enter:");
let mut line = String::new();
std::io::stdin()
.read_line(&mut line)
.context("failed to read authorization code from stdin")?;
line.trim().to_string()
}
};
if code.is_empty() {
anyhow::bail!("no authorization code provided");
}
println!("\n=== Stage 3: Token Exchange ===");
let auth = AuthorizationCode::new(
client_id,
client_secret,
redirect_uri,
code,
pkce.verifier(),
token_url,
);
let client = ForceClientBuilder::new().authenticate(auth).build().await?;
println!("Token exchange successful.\n");
println!("=== Authenticated Query ===");
let result: QueryResult<serde_json::Value> = client
.rest()
.query("SELECT Id, Name FROM Account ORDER BY CreatedDate DESC LIMIT 5")
.await?;
println!("Found {} account(s):", result.total_size);
for record in &result.records {
println!(" - {}", record["Name"].as_str().unwrap_or("(no name)"));
}
Ok(())
}
}
#[cfg(feature = "auth_code")]
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
example::run().await
}
#[cfg(not(feature = "auth_code"))]
fn main() {
eprintln!("This example requires the 'auth_code' feature.");
eprintln!("Run with: cargo run --example auth_code_pkce --features auth_code");
}