Skip to main content

zlayer_types/api/
variables.rs

1//! DTOs for the variables API.
2//!
3//! Variables are plaintext key-value pairs for template substitution in
4//! deployment specs. Unlike secrets, variable values are NOT encrypted and
5//! are fully visible in API responses.
6//!
7//! Variables can be global (`scope = None`) or project-scoped
8//! (`scope = Some(project_id)`).
9
10use serde::{Deserialize, Serialize};
11use utoipa::ToSchema;
12
13/// Query for `GET /api/v1/variables`.
14///
15/// `scope` selects the namespace:
16///   - omitted -> list global variables only (`scope IS NULL`).
17///   - any other value -> list variables belonging to that project/scope id.
18#[derive(Debug, Deserialize, Default)]
19pub struct ListVariablesQuery {
20    #[serde(default)]
21    pub scope: Option<String>,
22}
23
24/// Body for `POST /api/v1/variables`.
25#[derive(Debug, Serialize, Deserialize, ToSchema)]
26pub struct CreateVariableRequest {
27    /// Variable name (e.g. `"APP_VERSION"`). Must be unique within the chosen
28    /// scope.
29    pub name: String,
30    /// Plaintext value.
31    pub value: String,
32    /// Project id scope. `None` = global variable.
33    #[serde(default)]
34    pub scope: Option<String>,
35}
36
37/// Body for `PATCH /api/v1/variables/{id}`. All fields are optional.
38#[derive(Debug, Serialize, Deserialize, ToSchema)]
39pub struct UpdateVariableRequest {
40    /// New variable name. Will be re-checked for uniqueness.
41    #[serde(default)]
42    pub name: Option<String>,
43    /// New value.
44    #[serde(default)]
45    pub value: Option<String>,
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn test_create_request_deserialize_minimum() {
54        let req: CreateVariableRequest =
55            serde_json::from_str(r#"{"name":"FOO","value":"bar"}"#).unwrap();
56        assert_eq!(req.name, "FOO");
57        assert_eq!(req.value, "bar");
58        assert!(req.scope.is_none());
59    }
60
61    #[test]
62    fn test_create_request_deserialize_full() {
63        let req: CreateVariableRequest =
64            serde_json::from_str(r#"{"name":"FOO","value":"bar","scope":"proj-1"}"#).unwrap();
65        assert_eq!(req.name, "FOO");
66        assert_eq!(req.value, "bar");
67        assert_eq!(req.scope.as_deref(), Some("proj-1"));
68    }
69
70    #[test]
71    fn test_update_request_deserialize_partial() {
72        let req: UpdateVariableRequest = serde_json::from_str("{}").unwrap();
73        assert!(req.name.is_none());
74        assert!(req.value.is_none());
75
76        let req: UpdateVariableRequest = serde_json::from_str(r#"{"value":"new"}"#).unwrap();
77        assert!(req.name.is_none());
78        assert_eq!(req.value.as_deref(), Some("new"));
79    }
80
81    #[test]
82    fn test_list_query_default_is_empty() {
83        let q = ListVariablesQuery::default();
84        assert!(q.scope.is_none());
85    }
86
87    #[test]
88    fn test_list_query_construct_with_scope() {
89        let q = ListVariablesQuery {
90            scope: Some("p1".to_string()),
91        };
92        assert_eq!(q.scope.as_deref(), Some("p1"));
93    }
94}