1use crate::CLIState;
2use haste_fhir_client::http::{FHIRHttpClient, FHIRHttpState};
3use haste_fhir_model::r4::generated::terminology::IssueType;
4use haste_fhir_operation_error::OperationOutcomeError;
5use haste_server::auth_n::oidc::routes::discovery::WellKnownDiscoveryDocument;
6use std::{borrow::Cow, sync::Arc};
7use tokio::sync::Mutex;
8
9async fn config_to_fhir_http_state(
10 state: Arc<Mutex<CLIState>>,
11) -> Result<FHIRHttpState, OperationOutcomeError> {
12 let current_state = state.lock().await;
13 let Some(active_profile) = current_state.config.current_profile().cloned() else {
14 return Err(OperationOutcomeError::error(
15 IssueType::Invalid(None),
16 "No active profile set. Please set an active profile using the config command."
17 .to_string(),
18 ));
19 };
20
21 let state = state.clone();
22 let http_state = FHIRHttpState::new(
23 &active_profile.r4_url.clone(),
24 match active_profile.auth {
25 crate::commands::config::ProfileAuth::Public {} => None,
26 crate::commands::config::ProfileAuth::ClientCredentails {
27 client_id,
28 client_secret,
29 } => {
30 Some(Arc::new(move || {
31 let state = state.clone();
32 let client_id = client_id.clone();
33 let client_secret = client_secret.clone();
34 Box::pin(async move {
35 let mut current_state = state.lock().await;
36 if let Some(token) = current_state.access_token.clone() {
37 Ok(token)
38 } else {
39 let Some(active_profile) = current_state.config.current_profile()
40 else {
41 return Err(OperationOutcomeError::error(
42 IssueType::Invalid(None),
43 "No active profile set. Please set an active profile using the config command.".to_string(),
44 ));
45 };
46
47 let well_known_document: Cow<WellKnownDiscoveryDocument> =
48 if let Some(well_known_doc) = ¤t_state.well_known_document {
49 Cow::Borrowed(well_known_doc)
50 } else {
51 let res =
52 reqwest::get(&active_profile.oidc_discovery_uri).await;
53 let res = res.map_err(|e| {
54 OperationOutcomeError::error(
55 IssueType::Exception(None),
56 format!(
57 "Failed to fetch OIDC discovery document: {}",
58 e
59 ),
60 )
61 })?;
62
63 let well_known_document = serde_json::from_slice::<
64 WellKnownDiscoveryDocument,
65 >(
66 &res.bytes().await.map_err(|e| {
67 OperationOutcomeError::error(
68 IssueType::Exception(None),
69 format!(
70 "Failed to read OIDC discovery document: {}",
71 e
72 ),
73 )
74 })?,
75 )
76 .map_err(|e| {
77 OperationOutcomeError::error(
78 IssueType::Exception(None),
79 format!(
80 "Failed to parse OIDC discovery document: {}",
81 e
82 ),
83 )
84 })?;
85
86 current_state.well_known_document =
87 Some(well_known_document.clone());
88
89 Cow::Owned(well_known_document)
90 };
91
92 let params = [
94 ("grant_type", "client_credentials"),
95 ("client_id", &client_id),
96 ("client_secret", &client_secret),
97 ("scope", "openid system/*.*"),
98 ];
99
100 let res: reqwest::Response = reqwest::Client::new()
101 .post(&well_known_document.token_endpoint)
102 .form(¶ms)
103 .send()
104 .await
105 .map_err(|e| {
106 OperationOutcomeError::error(
107 IssueType::Exception(None),
108 format!("Failed to fetch access token: {}", e),
109 )
110 })?;
111
112 if !res.status().is_success() {
113 return Err(OperationOutcomeError::error(
114 IssueType::Forbidden(None),
115 format!(
116 "Failed to fetch access token: HTTP '{}'",
117 res.status(),
118 ),
119 ));
120 }
121
122 let token_response: serde_json::Value =
123 res.json().await.map_err(|e| {
124 OperationOutcomeError::error(
125 IssueType::Exception(None),
126 format!("Failed to parse access token response: {}", e),
127 )
128 })?;
129
130 let access_token = token_response
131 .get("access_token")
132 .and_then(|v| v.as_str())
133 .ok_or_else(|| {
134 OperationOutcomeError::error(
135 IssueType::Exception(None),
136 "No access_token field in token response".to_string(),
137 )
138 })?
139 .to_string();
140
141 current_state.access_token = Some(access_token.clone());
142
143 Ok(access_token)
144 }
145 })
146 }))
147 }
148 },
149 )?;
150
151 Ok(http_state)
152}
153
154pub async fn fhir_client(
155 state: Arc<Mutex<CLIState>>,
156) -> Result<Arc<FHIRHttpClient<()>>, OperationOutcomeError> {
157 let http_state = config_to_fhir_http_state(state).await?;
158 let fhir_client = Arc::new(FHIRHttpClient::<()>::new(http_state));
159
160 Ok(fhir_client)
161}