haste_health/cli/
config.rs1use haste_fhir_model::r4::generated::terminology::IssueType;
9use haste_fhir_operation_error::OperationOutcomeError;
10use serde::{Deserialize, Serialize};
11use std::path::PathBuf;
12
13pub(crate) const DEFAULT_CLIENT_CREDENTIALS_SCOPE: &str = "openid system/*.*";
15
16#[derive(Serialize, Deserialize, Debug, Default)]
17pub(crate) struct CliConfiguration {
18 pub(crate) active_profile: Option<String>,
19 pub(crate) profiles: Vec<Profile>,
20}
21
22impl CliConfiguration {
23 pub(crate) fn current_profile(&self) -> Option<&Profile> {
24 if let Some(active_profile_id) = self.active_profile.as_ref() {
25 self.profiles.iter().find(|p| &p.name == active_profile_id)
26 } else {
27 None
28 }
29 }
30
31 pub(crate) fn require_current_profile(&self) -> Result<&Profile, OperationOutcomeError> {
33 self.current_profile().ok_or_else(|| {
34 OperationOutcomeError::error(
35 IssueType::invalid(),
36 "No active profile. Pick one with `haste-health profile use`, or create one with `haste-health profile create`."
37 .to_string(),
38 )
39 })
40 }
41}
42
43#[derive(Serialize, Deserialize, Debug, Clone)]
45pub(crate) struct Profile {
46 pub(crate) name: String,
47 pub(crate) r4_url: String,
48 pub(crate) oidc_discovery_uri: String,
49 pub(crate) auth: ProfileAuth,
50}
51
52#[derive(Serialize, Deserialize, Debug, Clone)]
55pub(crate) enum ProfileAuth {
56 ClientCredentails {
58 client_id: String,
59 #[serde(default, skip_serializing_if = "Option::is_none")]
61 scope: Option<String>,
62 },
63 AuthorizationCode {
66 client_id: String,
67 redirect_uri: String,
68 scope: String,
69 },
70 Basic {
71 username: String,
72 },
73 Public {},
75}
76
77fn read_existing_config(location: &PathBuf) -> Result<CliConfiguration, OperationOutcomeError> {
78 let config_str = std::fs::read_to_string(location).map_err(|_| {
79 OperationOutcomeError::error(
80 IssueType::exception(),
81 format!(
82 "Failed to read config file at location '{}'",
83 location.to_string_lossy()
84 ),
85 )
86 })?;
87
88 toml::from_str::<CliConfiguration>(&config_str).map_err(|_| {
89 OperationOutcomeError::error(
90 IssueType::exception(),
91 format!(
92 "Failed to parse config file at location '{}'",
93 location.to_string_lossy()
94 ),
95 )
96 })
97}
98
99pub(crate) fn load_config(location: &PathBuf) -> CliConfiguration {
101 if let Ok(config) = read_existing_config(location) {
102 return config;
103 }
104
105 let config = CliConfiguration::default();
106 write_config(location, &config).expect("Failed to write default config file");
107 config
108}
109
110pub(crate) fn write_config(
111 location: &PathBuf,
112 config: &CliConfiguration,
113) -> Result<(), OperationOutcomeError> {
114 std::fs::write(location, toml::to_string(config).unwrap()).map_err(|_| {
115 OperationOutcomeError::error(
116 IssueType::exception(),
117 format!(
118 "Failed to write config file at location '{}'",
119 location.to_string_lossy()
120 ),
121 )
122 })
123}