golem_cli/model/deploy_diff/
api_definition.rs1use crate::log::LogColorize;
16use crate::model::api::to_method_pattern;
17use crate::model::app::HttpApiDefinitionName;
18use crate::model::app_raw::{
19 HttpApiDefinition, HttpApiDefinitionBindingType, HttpApiDefinitionRoute,
20};
21use crate::model::component::Component;
22use crate::model::deploy_diff::{DiffSerialize, ToYamlValueWithoutNulls};
23use crate::model::text::fmt::format_rib_source_for_error;
24use anyhow::anyhow;
25use golem_client::model::{
26 GatewayBindingComponent, GatewayBindingData, GatewayBindingType, HttpApiDefinitionRequest,
27 HttpApiDefinitionResponseData, RouteRequestData,
28};
29use serde::{Deserialize, Serialize};
30use std::collections::BTreeMap;
31
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub struct DiffableHttpApiDefinition(pub HttpApiDefinitionRequest);
34
35impl DiffableHttpApiDefinition {
36 pub fn from_server(api_definition: HttpApiDefinitionResponseData) -> anyhow::Result<Self> {
37 Ok(Self(HttpApiDefinitionRequest {
38 id: api_definition.id,
39 version: api_definition.version,
40 security: None, routes: api_definition
42 .routes
43 .into_iter()
44 .map(|route| RouteRequestData {
45 method: route.method,
46 path: route.path,
47 binding: GatewayBindingData {
48 binding_type: route.binding.binding_type,
49 component: route.binding.component.map(|component| {
50 GatewayBindingComponent {
51 name: component.name,
52 version: Some(component.version),
53 }
54 }),
55 worker_name: route.binding.worker_name,
56 idempotency_key: route.binding.idempotency_key,
57 response: route.binding.response,
58 invocation_context: route.binding.invocation_context,
59 },
60 security: route.security,
61 })
62 .collect(),
63 draft: api_definition.draft,
64 }))
65 }
66
67 pub fn from_manifest(
68 server_api_def: Option<&DiffableHttpApiDefinition>,
69 name: &HttpApiDefinitionName,
70 api_definition: &HttpApiDefinition,
71 latest_component_versions: &BTreeMap<String, Component>,
72 ) -> anyhow::Result<Self> {
73 let mut manifest_api_def = Self(HttpApiDefinitionRequest {
74 id: name.to_string(),
75 version: api_definition.version.clone(),
76 security: None, routes: api_definition
78 .routes
79 .iter()
80 .map(|route| normalize_http_api_route(latest_component_versions, route))
81 .collect::<Result<Vec<_>, _>>()?,
82 draft: true,
83 });
84
85 if let Some(server_api_def) = server_api_def {
87 if manifest_api_def.0.version == server_api_def.0.version
88 && !server_api_def.0.draft
89 && manifest_api_def.0.draft
90 {
91 manifest_api_def.0.draft = false;
92 }
93 }
94
95 Ok(manifest_api_def)
96 }
97}
98
99impl DiffSerialize for DiffableHttpApiDefinition {
100 fn to_diffable_string(&self) -> anyhow::Result<String> {
101 let yaml_value = self.0.clone().to_yaml_value_without_nulls()?;
102 Ok(serde_yaml::to_string(&yaml_value)?)
103 }
104}
105
106fn normalize_http_api_route(
107 latest_component_versions: &BTreeMap<String, Component>,
108 route: &HttpApiDefinitionRoute,
109) -> anyhow::Result<RouteRequestData> {
110 Ok(RouteRequestData {
111 method: to_method_pattern(&route.method)?,
112 path: normalize_http_api_binding_path(&route.path),
113 binding: GatewayBindingData {
114 binding_type: Some(
115 route
116 .binding
117 .type_
118 .as_ref()
119 .map(|binding_type| match binding_type {
120 HttpApiDefinitionBindingType::Default => GatewayBindingType::Default,
121 HttpApiDefinitionBindingType::CorsPreflight => {
122 GatewayBindingType::CorsPreflight
123 }
124 HttpApiDefinitionBindingType::FileServer => GatewayBindingType::FileServer,
125 HttpApiDefinitionBindingType::HttpHandler => {
126 GatewayBindingType::HttpHandler
127 }
128 })
129 .unwrap_or_else(|| GatewayBindingType::Default),
130 ),
131 component: {
132 route
133 .binding
134 .component_name
135 .as_ref()
136 .map(|name| GatewayBindingComponent {
137 name: name.clone(),
138 version: route.binding.component_version.or_else(|| {
139 latest_component_versions
140 .get(name)
141 .map(|component| component.versioned_component_id.version)
142 }),
143 })
144 },
145 worker_name: None,
146 idempotency_key: normalize_rib_property(&route.binding.idempotency_key)?,
147 invocation_context: normalize_rib_property(&route.binding.invocation_context)?,
148 response: normalize_rib_property(&route.binding.response)?,
149 },
150 security: route.security.clone(),
151 })
152}
153
154fn normalize_rib_property(rib: &Option<String>) -> anyhow::Result<Option<String>> {
155 rib.as_ref()
156 .map(|r| r.as_str())
157 .map(normalize_rib_source_code)
158 .transpose()
159}
160
161pub fn normalize_http_api_binding_path(path: &str) -> String {
162 path.to_string()
163 .strip_suffix("/")
164 .unwrap_or(path)
165 .to_string()
166}
167
168fn normalize_rib_source_code(rib: &str) -> anyhow::Result<String> {
169 Ok(rib::from_string(rib)
170 .map_err(|err| {
171 anyhow!(
172 "Failed to normalize Rib source code: {}\n{}\n{}",
173 err,
174 "Rib source:".log_color_highlight(),
175 format_rib_source_for_error(&err, rib)
176 )
177 })?
178 .to_string())
179}