Skip to main content

scconfig_rs/
request.rs

1use crate::{Error, Result};
2
3/// Output format for the Spring alternative-format environment endpoints.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum EnvironmentFormat {
6    /// YAML output using the `.yml` suffix.
7    Yml,
8    /// YAML output using the `.yaml` suffix.
9    Yaml,
10    /// Java properties output using the `.properties` suffix.
11    Properties,
12}
13
14impl EnvironmentFormat {
15    pub(crate) fn suffix(self) -> &'static str {
16        match self {
17            Self::Yml => ".yml",
18            Self::Yaml => ".yaml",
19            Self::Properties => ".properties",
20        }
21    }
22}
23
24/// A request for the Spring `Environment` endpoint.
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct EnvironmentRequest {
27    application: String,
28    profiles: Vec<String>,
29    label: Option<String>,
30    resolve_placeholders: bool,
31}
32
33impl EnvironmentRequest {
34    /// Creates a new environment request.
35    pub fn new<A, I, P>(application: A, profiles: I) -> Result<Self>
36    where
37        A: Into<String>,
38        I: IntoIterator<Item = P>,
39        P: Into<String>,
40    {
41        let application = sanitize_required(application.into(), Error::EmptyApplication)?;
42        let profiles = sanitize_profiles(profiles)?;
43
44        Ok(Self {
45            application,
46            profiles,
47            label: None,
48            resolve_placeholders: false,
49        })
50    }
51
52    /// Overrides the git label, branch, tag, or commit for this request.
53    pub fn label(mut self, label: impl Into<String>) -> Self {
54        let label = label.into().trim().to_string();
55        self.label = if label.is_empty() { None } else { Some(label) };
56        self
57    }
58
59    /// Controls the `resolvePlaceholders` query parameter used by YAML and properties endpoints.
60    pub fn resolve_placeholders(mut self, enabled: bool) -> Self {
61        self.resolve_placeholders = enabled;
62        self
63    }
64
65    /// Returns the application name.
66    pub fn application(&self) -> &str {
67        &self.application
68    }
69
70    /// Returns the active profiles in request order.
71    pub fn profiles(&self) -> &[String] {
72        &self.profiles
73    }
74
75    /// Returns the explicit label, when set.
76    pub fn label_ref(&self) -> Option<&str> {
77        self.label.as_deref()
78    }
79
80    /// Returns whether placeholder resolution should be requested for alternative formats.
81    pub fn resolve_placeholders_enabled(&self) -> bool {
82        self.resolve_placeholders
83    }
84
85    pub(crate) fn joined_profiles(&self) -> String {
86        self.profiles.join(",")
87    }
88}
89
90/// A request for the plain-text Spring resource endpoint.
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct ResourceRequest {
93    application: String,
94    profiles: Vec<String>,
95    label: Option<String>,
96    path: String,
97}
98
99impl ResourceRequest {
100    /// Creates a new resource request.
101    pub fn new<A, I, P, R>(application: A, profiles: I, path: R) -> Result<Self>
102    where
103        A: Into<String>,
104        I: IntoIterator<Item = P>,
105        P: Into<String>,
106        R: Into<String>,
107    {
108        let application = sanitize_required(application.into(), Error::EmptyApplication)?;
109        let profiles = sanitize_profiles(profiles)?;
110        let path = sanitize_required(
111            normalize_resource_path(path.into()),
112            Error::EmptyResourcePath,
113        )?;
114
115        Ok(Self {
116            application,
117            profiles,
118            label: None,
119            path,
120        })
121    }
122
123    /// Overrides the git label, branch, tag, or commit for this request.
124    pub fn label(mut self, label: impl Into<String>) -> Self {
125        let label = label.into().trim().to_string();
126        self.label = if label.is_empty() { None } else { Some(label) };
127        self
128    }
129
130    /// Returns the application name.
131    pub fn application(&self) -> &str {
132        &self.application
133    }
134
135    /// Returns the active profiles in request order.
136    pub fn profiles(&self) -> &[String] {
137        &self.profiles
138    }
139
140    /// Returns the explicit label, when set.
141    pub fn label_ref(&self) -> Option<&str> {
142        self.label.as_deref()
143    }
144
145    /// Returns the resource path.
146    pub fn path(&self) -> &str {
147        &self.path
148    }
149
150    pub(crate) fn joined_profiles(&self) -> String {
151        self.profiles.join(",")
152    }
153
154    pub(crate) fn path_segments(&self) -> Vec<String> {
155        self.path
156            .split('/')
157            .filter(|segment| !segment.is_empty())
158            .map(ToOwned::to_owned)
159            .collect()
160    }
161}
162
163fn sanitize_profiles<I, P>(profiles: I) -> Result<Vec<String>>
164where
165    I: IntoIterator<Item = P>,
166    P: Into<String>,
167{
168    let sanitized: Vec<String> = profiles
169        .into_iter()
170        .map(Into::into)
171        .map(|profile| profile.trim().to_string())
172        .filter(|profile| !profile.is_empty())
173        .collect();
174
175    if sanitized.is_empty() {
176        Err(Error::EmptyProfiles)
177    } else {
178        Ok(sanitized)
179    }
180}
181
182fn sanitize_required(value: String, error: Error) -> Result<String> {
183    let value = value.trim().to_string();
184    if value.is_empty() {
185        Err(error)
186    } else {
187        Ok(value)
188    }
189}
190
191fn normalize_resource_path(path: String) -> String {
192    path.replace('\\', "/").trim_matches('/').to_string()
193}