use serde::de::{ Error, Unexpected, }; use serde_json::{ json, Result, Value }; use bon::Builder; trait Metadata { fn format_metadata(self, required: RequiredMetadata) -> Result; } pub struct RequiredMetadata { issuer: String, authorization_endpoint: String, token_endpoint: String, } impl RequiredMetadata { fn new( issuer: String, authorization_endpoint: String, token_endpoint: String ) -> Self { RequiredMetadata { issuer, authorization_endpoint, token_endpoint } } } #[derive(Builder)] struct AtprotoMetadata { additional_response_types_supported: Option>, additional_grant_types_supported: Option>, additional_code_challenge_methods_supported: Option>, additional_token_endpoint_auth_methods_supported: Option>, additional_token_endpoint_auth_signing_alg_values_supported: Option>, additional_scopes_supported: Option>, pushed_authorization_request_endpoint: String, additional_dpop_signing_alg_values_supported: Option>, } impl AtprotoMetadata { fn check_fields(&self) -> Result<()> { // TODO: Issuer check (https scheme, no default port, no path segments if self.additional_token_endpoint_auth_signing_alg_values_supported .as_ref() .is_none_or(|vec| vec.iter().any(|s| s == "none")) { return Err(Error::invalid_value( Unexpected::Other("\"none\" in token_endpoint_auth_signing_alg_values_supported"), &"\"none\" to be omitted from token_endpoint_auth_signing_alg_values_supported" )); } Ok(()) } } impl Metadata for AtprotoMetadata { fn format_metadata(self, required: RequiredMetadata) -> Result { self.check_fields()?; Ok(json!({ "issuer": required.issuer, "authorization_endpoint": required.authorization_endpoint, "token_endpoint": required.token_endpoint, "response_types_supported": self.additional_response_types_supported.unwrap_or_default() .extend(["code".to_string()]), "grant_types_supported": self.additional_grant_types_supported.unwrap_or_default() .extend([ "authorization_code".to_string(), "refresh_token".to_string() ]), "code_challenge_methods_supported": self.additional_code_challenge_methods_supported.unwrap_or_default() .extend(["S256".to_string()]), "token_endpoint_auth_methods_supported": self.additional_token_endpoint_auth_methods_supported.unwrap_or_default() .extend([ "none".to_string(), "private_key_jwt".to_string() ]), "token_endpoint_auth_signing_alg_values_supported": self.additional_token_endpoint_auth_signing_alg_values_supported.unwrap_or_default() .extend(["ES256".to_string()]), "scopes_supported": self.additional_scopes_supported.unwrap_or_default() .extend(["atproto".to_string()]), "authorization_response_iss_parameter_supported": true, "require_pushed_authorization_requests": true, "pushed_authorization_request_endpoint": self.pushed_authorization_request_endpoint, "dpop_signing_alg_values_supported": self.additional_dpop_signing_alg_values_supported.unwrap_or_default() .extend(["ES256".to_string()]), "client_id_metadata_document_supported": true, })) } }