use clap::builder::{TryMapValueParser, TypedValueParser, ValueParserFactory}; use clio::CachedInput; use eyre::Context; use futures::future::try_join_all; use indexmap::IndexMap; use openidconnect::{ core::{CoreClient, CoreProviderMetadata}, ClientId, ClientSecret, IssuerUrl, Scope, }; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Debug, Clone)] pub struct Config { pub oidc: IndexMap, } impl ValueParserFactory for Config { type Parser = TryMapValueParser< ::Parser, fn(CachedInput) -> eyre::Result, >; fn value_parser() -> Self::Parser { CachedInput::value_parser().try_map(Config::load) } } impl Config { pub const EXAMPLE: &'static str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/config.toml.sample")); pub fn load_str(input: &str, path: impl ToString) -> eyre::Result { toml::from_str(input).wrap_err_with(|| path.to_string()) } pub fn load(input: CachedInput) -> eyre::Result { let s = std::str::from_utf8(input.get_data()).wrap_err_with(|| input.path().to_string())?; toml::from_str(s).wrap_err_with(|| input.path().to_string()) } pub async fn resolve(&mut self) -> eyre::Result<()> { try_join_all(self.oidc.iter_mut().map(|(_, provider)| provider.resolve())).await?; Ok(()) } } #[derive(Debug, Clone)] pub struct OIDCProviderState { pub client: CoreClient, pub provider_metadata: CoreProviderMetadata, } #[derive(Deserialize, Serialize, Debug, Clone)] pub struct OIDCProvider { pub pretty_name: String, pub issuer_url: IssuerUrl, pub client_id: ClientId, pub secret: ClientSecret, pub scopes: Vec, #[serde(skip)] state: Option, } impl OIDCProvider { pub fn state(&self) -> &OIDCProviderState { self.state.as_ref().expect("resolve called by main") } pub async fn resolve(&mut self) -> eyre::Result<()> { let provider_metadata = CoreProviderMetadata::discover_async( self.issuer_url.clone(), openidconnect::reqwest::async_http_client, ) .await?; let client = CoreClient::from_provider_metadata( provider_metadata.clone(), self.client_id.clone(), Some(self.secret.clone()), ); let client = client.disable_openid_scope(); self.state = Some(OIDCProviderState { client, provider_metadata, }); Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_example() -> eyre::Result<()> { Config::load_str(Config::EXAMPLE, "config.toml.sample")?; Ok(()) } }