use crate::config::ConfigEntity;
use crate::error::{Error, Result};
use crate::paths;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ActionType {
Api,
Command,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "UPPERCASE")]
pub enum HttpMethod {
#[default]
Get,
Post,
Put,
Patch,
Delete,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleManifest {
#[serde(default, skip_serializing)]
pub id: String,
pub name: String,
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub author: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub homepage: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub config_schema: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub default_pinned_files: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub default_pinned_logs: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub database: Option<DatabaseConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cli: Option<CliConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub discovery: Option<DiscoveryConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub deploy: Vec<DeployVerification>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub deploy_override: Vec<DeployOverride>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub version_patterns: Vec<VersionPatternConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub build: Option<BuildConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub commands: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub runtime: Option<RuntimeConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub inputs: Vec<InputConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output: Option<OutputConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub actions: Vec<ActionConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub settings: Vec<SettingConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub requires: Option<RequirementsConfig>,
#[serde(
flatten,
default,
skip_serializing_if = "std::collections::HashMap::is_empty"
)]
pub extra: std::collections::HashMap<String, serde_json::Value>,
#[serde(skip)]
pub module_path: Option<String>,
}
impl ModuleManifest {
pub fn has_cli(&self) -> bool {
self.cli.is_some()
}
pub fn has_runtime(&self) -> bool {
self.runtime.is_some()
}
pub fn has_build(&self) -> bool {
self.build.is_some()
}
}
impl ConfigEntity for ModuleManifest {
const ENTITY_TYPE: &'static str = "module";
const DIR_NAME: &'static str = "modules";
fn id(&self) -> &str {
&self.id
}
fn set_id(&mut self, id: String) {
self.id = id;
}
fn not_found_error(id: String, suggestions: Vec<String>) -> Error {
Error::module_not_found(id, suggestions)
}
fn config_path(id: &str) -> Result<PathBuf> {
paths::module_manifest(id)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequirementsConfig {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub modules: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub components: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub cli: Option<DatabaseCliConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseCliConfig {
pub tables_command: String,
pub describe_command: String,
pub query_command: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CliConfig {
pub tool: String,
pub display_name: String,
pub command_template: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_cli_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub working_dir_template: Option<String>,
#[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
pub settings_flags: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiscoveryConfig {
pub find_command: String,
pub base_path_transform: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name_command: Option<String>,
}
impl DiscoveryConfig {
pub fn transform_to_base_path(&self, path: &str) -> String {
match self.base_path_transform.as_str() {
"dirname" => std::path::Path::new(path)
.parent()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| path.to_string()),
_ => path.to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeployVerification {
pub path_pattern: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub verify_command: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub verify_error_message: Option<String>,
}
fn default_staging_path() -> String {
"/tmp/homeboy-staging".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeployOverride {
pub path_pattern: String,
#[serde(default = "default_staging_path")]
pub staging_path: String,
pub install_command: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub cleanup_command: Option<String>,
#[serde(default)]
pub skip_permissions_fix: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionPatternConfig {
pub extension: String,
pub pattern: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuildConfig {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub artifact_extensions: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub script_names: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub command_template: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub module_script: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pre_build_script: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub artifact_pattern: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuntimeConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub run_command: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub setup_command: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ready_check: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub env: Option<std::collections::HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub entrypoint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub args: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_site: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputConfig {
pub id: String,
#[serde(rename = "type")]
pub input_type: String,
pub label: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub placeholder: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<Vec<SelectOption>>,
pub arg: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SelectOption {
pub value: String,
pub label: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputConfig {
pub schema: OutputSchema,
pub display: String,
pub selectable: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputSchema {
#[serde(rename = "type")]
pub schema_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<std::collections::HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionConfig {
pub id: String,
pub label: String,
#[serde(rename = "type")]
pub action_type: ActionType,
#[serde(skip_serializing_if = "Option::is_none")]
pub endpoint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<HttpMethod>,
#[serde(skip_serializing_if = "Option::is_none")]
pub requires_auth: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payload: Option<std::collections::HashMap<String, serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub command: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SettingConfig {
pub id: String,
#[serde(rename = "type")]
pub setting_type: String,
pub label: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub placeholder: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<serde_json::Value>,
}