use crate::log::LogColorize;
use crate::model::api::to_method_pattern;
use crate::model::app::HttpApiDefinitionName;
use crate::model::app_raw::{
HttpApiDefinition, HttpApiDefinitionBindingType, HttpApiDefinitionRoute,
};
use crate::model::component::Component;
use crate::model::deploy_diff::{DiffSerialize, ToYamlValueWithoutNulls};
use crate::model::text::fmt::format_rib_source_for_error;
use anyhow::anyhow;
use golem_client::model::{
GatewayBindingComponent, GatewayBindingData, GatewayBindingType, HttpApiDefinitionRequest,
HttpApiDefinitionResponseData, RouteRequestData,
};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DiffableHttpApiDefinition(pub HttpApiDefinitionRequest);
impl DiffableHttpApiDefinition {
pub fn from_server(api_definition: HttpApiDefinitionResponseData) -> anyhow::Result<Self> {
Ok(Self(HttpApiDefinitionRequest {
id: api_definition.id,
version: api_definition.version,
security: None, routes: api_definition
.routes
.into_iter()
.map(|route| RouteRequestData {
method: route.method,
path: route.path,
binding: GatewayBindingData {
binding_type: route.binding.binding_type,
component: route.binding.component.map(|component| {
GatewayBindingComponent {
name: component.name,
version: Some(component.version),
}
}),
worker_name: route.binding.worker_name,
idempotency_key: route.binding.idempotency_key,
response: route.binding.response,
invocation_context: route.binding.invocation_context,
},
security: route.security,
})
.collect(),
draft: api_definition.draft,
}))
}
pub fn from_manifest(
server_api_def: Option<&DiffableHttpApiDefinition>,
name: &HttpApiDefinitionName,
api_definition: &HttpApiDefinition,
latest_component_versions: &BTreeMap<String, Component>,
) -> anyhow::Result<Self> {
let mut manifest_api_def = Self(HttpApiDefinitionRequest {
id: name.to_string(),
version: api_definition.version.clone(),
security: None, routes: api_definition
.routes
.iter()
.map(|route| normalize_http_api_route(latest_component_versions, route))
.collect::<Result<Vec<_>, _>>()?,
draft: true,
});
if let Some(server_api_def) = server_api_def {
if manifest_api_def.0.version == server_api_def.0.version
&& !server_api_def.0.draft
&& manifest_api_def.0.draft
{
manifest_api_def.0.draft = false;
}
}
Ok(manifest_api_def)
}
}
impl DiffSerialize for DiffableHttpApiDefinition {
fn to_diffable_string(&self) -> anyhow::Result<String> {
let yaml_value = self.0.clone().to_yaml_value_without_nulls()?;
Ok(serde_yaml::to_string(&yaml_value)?)
}
}
fn normalize_http_api_route(
latest_component_versions: &BTreeMap<String, Component>,
route: &HttpApiDefinitionRoute,
) -> anyhow::Result<RouteRequestData> {
Ok(RouteRequestData {
method: to_method_pattern(&route.method)?,
path: normalize_http_api_binding_path(&route.path),
binding: GatewayBindingData {
binding_type: Some(
route
.binding
.type_
.as_ref()
.map(|binding_type| match binding_type {
HttpApiDefinitionBindingType::Default => GatewayBindingType::Default,
HttpApiDefinitionBindingType::CorsPreflight => {
GatewayBindingType::CorsPreflight
}
HttpApiDefinitionBindingType::FileServer => GatewayBindingType::FileServer,
HttpApiDefinitionBindingType::HttpHandler => {
GatewayBindingType::HttpHandler
}
HttpApiDefinitionBindingType::SwaggerUi => GatewayBindingType::SwaggerUi,
})
.unwrap_or_else(|| GatewayBindingType::Default),
),
component: {
route
.binding
.component_name
.as_ref()
.map(|name| GatewayBindingComponent {
name: name.clone(),
version: route.binding.component_version.or_else(|| {
latest_component_versions
.get(name)
.map(|component| component.versioned_component_id.version)
}),
})
},
worker_name: None,
idempotency_key: normalize_rib_property(&route.binding.idempotency_key)?,
invocation_context: normalize_rib_property(&route.binding.invocation_context)?,
response: normalize_rib_property(&route.binding.response)?,
},
security: route.security.clone(),
})
}
fn normalize_rib_property(rib: &Option<String>) -> anyhow::Result<Option<String>> {
rib.as_ref()
.map(|r| r.as_str())
.map(normalize_rib_source_code)
.transpose()
}
pub fn normalize_http_api_binding_path(path: &str) -> String {
path.to_string()
.strip_suffix("/")
.unwrap_or(path)
.to_string()
}
fn normalize_rib_source_code(rib: &str) -> anyhow::Result<String> {
Ok(rib::from_string(rib)
.map_err(|err| {
anyhow!(
"Failed to normalize Rib source code: {}\n{}\n{}",
err,
"Rib source:".log_color_highlight(),
format_rib_source_for_error(&err, rib)
)
})?
.to_string())
}