use std::collections::HashMap;
use std::path::{Path, PathBuf};
use asset_container::{Asset, AssetFlags};
use url::Url;
use wick_asset_reference::AssetReference;
use wick_packet::RuntimeConfig;
use super::template_config::Renderable;
use crate::config::TemplateConfig;
use crate::error::ManifestError;
crate::impl_from_for!(ResourceDefinition, TcpPort);
crate::impl_from_for!(ResourceDefinition, UdpPort);
crate::impl_from_for!(ResourceDefinition, Volume);
crate::impl_from_for!(ResourceDefinition, Url, UrlResource);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResourceKind {
TcpPort,
UdpPort,
Url,
Volume,
}
impl std::fmt::Display for ResourceKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::TcpPort => write!(f, "TcpPort"),
Self::UdpPort => write!(f, "UdpPort"),
Self::Url => write!(f, "Url"),
Self::Volume => write!(f, "Volume"),
}
}
}
#[derive(Debug, Clone, derive_asset_container::AssetManager, serde::Serialize, PartialEq, Hash, Eq)]
#[asset(asset(AssetReference))]
#[serde(rename_all = "kebab-case")]
pub enum ResourceDefinition {
#[asset(skip)]
TcpPort(TcpPort),
#[asset(skip)]
UdpPort(UdpPort),
#[asset(skip)]
Url(UrlResource),
Volume(Volume),
}
impl Renderable for ResourceDefinition {
fn render_config(
&mut self,
source: Option<&Path>,
root_config: Option<&RuntimeConfig>,
env: Option<&HashMap<String, String>>,
) -> Result<(), ManifestError> {
match self {
ResourceDefinition::TcpPort(v) => v.render_config(source, root_config, env),
ResourceDefinition::UdpPort(v) => v.render_config(source, root_config, env),
ResourceDefinition::Url(v) => v.render_config(source, root_config, env),
ResourceDefinition::Volume(v) => v.render_config(source, root_config, env),
}
}
}
impl ResourceDefinition {
#[must_use]
pub const fn kind(&self) -> ResourceKind {
match self {
ResourceDefinition::TcpPort(_) => ResourceKind::TcpPort,
ResourceDefinition::UdpPort(_) => ResourceKind::UdpPort,
ResourceDefinition::Url(_) => ResourceKind::Url,
ResourceDefinition::Volume(_) => ResourceKind::Volume,
}
}
pub fn try_tcpport(self) -> Result<TcpPort, ManifestError> {
self.try_into()
}
pub fn try_udpport(self) -> Result<UdpPort, ManifestError> {
self.try_into()
}
pub fn try_url(self) -> Result<UrlResource, ManifestError> {
self.try_into()
}
pub fn try_volume(self) -> Result<Volume, ManifestError> {
self.try_into()
}
}
impl TryFrom<String> for UrlResource {
type Error = crate::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
url::Url::parse(&value)
.map_err(|_| Self::Error::InvalidUrl(value.clone()))
.map(Self::new)
}
}
#[derive(Debug, Clone, PartialEq, Hash, Eq, derive_builder::Builder, serde::Serialize)]
#[must_use]
pub struct Volume {
path: TemplateConfig<AssetReference>,
}
impl Volume {
pub fn new(template: String) -> Self {
let template = TemplateConfig::new_template(template);
Self { path: template }
}
#[cfg(feature = "v1")]
pub(crate) fn unrender(&self) -> Result<String, ManifestError> {
self.path.unrender()
}
pub fn path(&self) -> Result<PathBuf, ManifestError> {
if let Some(path) = &self.path.value {
Ok(path.path()?)
} else {
Err(ManifestError::UnrenderedConfiguration(format!(
"{:?}",
self.path.template
)))
}
}
}
impl Renderable for Volume {
fn render_config(
&mut self,
source: Option<&Path>,
root_config: Option<&RuntimeConfig>,
env: Option<&HashMap<String, String>>,
) -> Result<(), ManifestError> {
self.path.set_value(self.path.render(source, root_config, env)?);
Ok(())
}
}
impl asset_container::AssetManager for Volume {
type Asset = AssetReference;
fn assets(&self) -> asset_container::Assets<Self::Asset> {
asset_container::Assets::new(
self
.path
.value
.as_ref()
.map_or_else(Vec::new, |v| vec![std::borrow::Cow::Borrowed(v)]),
self.get_asset_flags(),
)
}
fn set_baseurl(&self, baseurl: &Path) {
if let Some(path) = &self.path.value {
path.update_baseurl(baseurl);
match self.path() {
Ok(p) => {
if !p.is_dir() {
tracing::warn!(%path,"volume path is not a directory");
}
}
Err(e) => {
tracing::warn!(%path,error=%e,"volume path could not be resolved");
}
}
}
}
fn get_asset_flags(&self) -> u32 {
AssetFlags::Lazy.bits()
}
}
#[derive(Debug, Clone, PartialEq, Hash, Eq, property::Property, serde::Serialize)]
#[must_use]
#[property(get(public), set(private), mut(disable))]
pub struct UrlResource {
pub(crate) url: TemplateConfig<Url>,
}
impl UrlResource {
pub const fn new(url: Url) -> Self {
Self {
url: TemplateConfig::new_value(url),
}
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn into_inner(self) -> TemplateConfig<Url> {
self.url
}
}
impl std::fmt::Display for UrlResource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.url)
}
}
impl Renderable for UrlResource {
fn render_config(
&mut self,
source: Option<&Path>,
root_config: Option<&RuntimeConfig>,
env: Option<&HashMap<String, String>>,
) -> Result<(), ManifestError> {
self.url.set_value(self.url.render(source, root_config, env)?);
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Hash, Eq, property::Property, serde::Serialize)]
#[property(get(public), set(private), mut(disable))]
pub struct TcpPort {
pub(crate) port: TemplateConfig<u16>,
pub(crate) host: TemplateConfig<String>,
}
impl TcpPort {
pub fn new<T: Into<String>>(host: T, port: u16) -> Self {
Self {
port: TemplateConfig::new_value(port),
host: TemplateConfig::new_value(host.into()),
}
}
#[must_use]
pub fn address(&self) -> String {
format!("{}:{}", self.host, self.port)
}
}
impl Renderable for TcpPort {
fn render_config(
&mut self,
source: Option<&Path>,
root_config: Option<&RuntimeConfig>,
env: Option<&HashMap<String, String>>,
) -> Result<(), ManifestError> {
self.port.set_value(self.port.render(source, root_config, env)?);
self.host.set_value(self.host.render(source, root_config, env)?);
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Hash, Eq, property::Property, serde::Serialize)]
#[property(get(public), set(private), mut(disable))]
pub struct UdpPort {
pub(crate) port: TemplateConfig<u16>,
pub(crate) host: TemplateConfig<String>,
}
impl UdpPort {
pub fn new<T: Into<String>>(host: T, port: u16) -> Self {
Self {
port: TemplateConfig::new_value(port),
host: TemplateConfig::new_value(host.into()),
}
}
#[must_use]
pub fn address(&self) -> String {
format!("{}:{}", self.host, self.port)
}
}
impl Renderable for UdpPort {
fn render_config(
&mut self,
source: Option<&Path>,
root_config: Option<&RuntimeConfig>,
env: Option<&HashMap<String, String>>,
) -> Result<(), ManifestError> {
self.port.set_value(self.port.render(source, root_config, env)?);
self.host.set_value(self.host.render(source, root_config, env)?);
Ok(())
}
}
#[cfg(test)]
mod test {
use anyhow::Result;
use super::*;
#[test]
fn try_into_urlresource() -> Result<()> {
let urlstr = "postgres://postgres:password!12345@nas.glhf.lan:55432/wick_test".to_owned();
let url_resource: UrlResource = urlstr.clone().try_into()?;
let url: Url = urlstr.parse()?;
assert_eq!(url_resource.url.value_unchecked().as_str(), &urlstr);
assert_eq!(url_resource.url.value_unchecked(), &url);
Ok(())
}
}