golem_cli/model/deploy_diff/
api_deployment.rs

1// Copyright 2024-2025 Golem Cloud
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::model::api::ApiDeployment;
16use crate::model::app_raw::HttpApiDeployment;
17use crate::model::deploy_diff::DiffSerialize;
18use serde::{Deserialize, Serialize};
19use std::collections::{BTreeMap, BTreeSet};
20use std::ops::Sub;
21
22#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
23#[serde(rename_all = "camelCase", deny_unknown_fields)]
24pub struct DiffableHttpApiDeployment {
25    pub host: String,
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub subdomain: Option<String>,
28    #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
29    pub definitions: BTreeSet<String>,
30}
31
32impl DiffableHttpApiDeployment {
33    pub fn from_server(api_deployment: ApiDeployment) -> anyhow::Result<Self> {
34        Ok(DiffableHttpApiDeployment {
35            host: api_deployment.site.host,
36            subdomain: api_deployment.site.subdomain,
37            definitions: api_deployment
38                .api_definitions
39                .iter()
40                .map(|api_def| format!("{}@{}", api_def.id, api_def.version))
41                .collect(),
42        })
43    }
44
45    pub fn from_manifest(
46        api_deployment: &HttpApiDeployment,
47        latest_api_definition_versions: &BTreeMap<String, String>,
48    ) -> anyhow::Result<Self> {
49        Ok(DiffableHttpApiDeployment {
50            host: api_deployment.host.clone(),
51            subdomain: api_deployment.subdomain.clone(),
52            definitions: api_deployment
53                .definitions
54                .iter()
55                .map(|api_def| {
56                    if api_def.contains("@") {
57                        api_def.clone()
58                    } else {
59                        format!(
60                            "{}@{}",
61                            api_def,
62                            latest_api_definition_versions
63                                .get(api_def)
64                                .expect("Missing latest version for HTTP API definition")
65                        )
66                    }
67                })
68                .collect(),
69        })
70    }
71
72    pub fn definitions(&self) -> impl Iterator<Item = (&str, &str)> {
73        self.definitions.iter().map(to_name_and_version)
74    }
75
76    pub fn plan(&self, new: &Self) -> HttpApiDeploymentUpdatePlan {
77        HttpApiDeploymentUpdatePlan {
78            delete: self
79                .definitions
80                .sub(&new.definitions)
81                .iter()
82                .map(to_name_and_version_owned)
83                .collect(),
84            add: new
85                .definitions
86                .sub(&self.definitions)
87                .iter()
88                .map(to_name_and_version_owned)
89                .collect(),
90        }
91    }
92}
93
94impl DiffSerialize for DiffableHttpApiDeployment {
95    fn to_diffable_string(&self) -> anyhow::Result<String> {
96        Ok(serde_yaml::to_string(&self)?)
97    }
98}
99
100pub struct HttpApiDeploymentUpdatePlan {
101    pub delete: BTreeSet<(String, String)>,
102    pub add: BTreeSet<(String, String)>,
103}
104
105fn to_name_and_version<S: AsRef<str>>(api_def_with_version: &S) -> (&str, &str) {
106    let mut parts = api_def_with_version.as_ref().split('@');
107    (
108        parts.next().expect("missing API name"),
109        parts.next().expect("missing version"),
110    )
111}
112
113fn to_name_and_version_owned<S: AsRef<str>>(api_def_with_version: &S) -> (String, String) {
114    let (name, version) = to_name_and_version(api_def_with_version);
115    (name.to_owned(), version.to_owned())
116}