use crate::config::ConfigEntity;
use crate::error::{Error, Result};
use crate::paths;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ActionType {
Api,
Command,
Builtin,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum BuiltinAction {
CopyColumn,
ExportCsv,
CopyJson,
}
#[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 DeployCapability {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub verifications: Vec<DeployVerification>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub overrides: Vec<DeployOverride>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub version_patterns: Vec<VersionPatternConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub since_tag: Option<SinceTagConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditCapability {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ignore_claim_patterns: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub feature_patterns: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutableCapability {
pub runtime: RuntimeConfig,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub inputs: Vec<InputConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output: Option<OutputConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlatformCapability {
#[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 discovery: Option<DiscoveryConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub commands: Vec<String>,
}
#[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 icon: Option<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 deploy: Option<DeployCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audit: Option<AuditCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub executable: Option<ExecutableCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub platform: Option<PlatformCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cli: Option<CliConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub build: Option<BuildConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lint: Option<LintConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub test: Option<TestConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub actions: Vec<ActionConfig>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub hooks: HashMap<String, Vec<String>>,
#[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 = "HashMap::is_empty")]
pub extra: 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_build(&self) -> bool {
self.build.is_some()
}
pub fn has_lint(&self) -> bool {
self.lint
.as_ref()
.and_then(|c| c.module_script.as_ref())
.is_some()
}
pub fn has_test(&self) -> bool {
self.test
.as_ref()
.and_then(|c| c.module_script.as_ref())
.is_some()
}
pub fn lint_script(&self) -> Option<&str> {
self.lint.as_ref().and_then(|c| c.module_script.as_deref())
}
pub fn test_script(&self) -> Option<&str> {
self.test.as_ref().and_then(|c| c.module_script.as_deref())
}
pub fn deploy_verifications(&self) -> &[DeployVerification] {
self.deploy
.as_ref()
.map(|d| d.verifications.as_slice())
.unwrap_or(&[])
}
pub fn deploy_overrides(&self) -> &[DeployOverride] {
self.deploy
.as_ref()
.map(|d| d.overrides.as_slice())
.unwrap_or(&[])
}
pub fn version_patterns(&self) -> &[VersionPatternConfig] {
self.deploy
.as_ref()
.map(|d| d.version_patterns.as_slice())
.unwrap_or(&[])
}
pub fn since_tag(&self) -> Option<&SinceTagConfig> {
self.deploy.as_ref().and_then(|d| d.since_tag.as_ref())
}
pub fn runtime(&self) -> Option<&RuntimeConfig> {
self.executable.as_ref().map(|e| &e.runtime)
}
pub fn inputs(&self) -> &[InputConfig] {
self.executable
.as_ref()
.map(|e| e.inputs.as_slice())
.unwrap_or(&[])
}
pub fn audit_ignore_claim_patterns(&self) -> &[String] {
self.audit
.as_ref()
.map(|a| a.ignore_claim_patterns.as_slice())
.unwrap_or(&[])
}
pub fn audit_feature_patterns(&self) -> &[String] {
self.audit
.as_ref()
.map(|a| a.feature_patterns.as_slice())
.unwrap_or(&[])
}
pub fn database(&self) -> Option<&DatabaseConfig> {
self.platform.as_ref().and_then(|p| p.database.as_ref())
}
}
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, Default)]
pub struct CliHelpConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub project_id_help: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub args_help: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub examples: Vec<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 = "HashMap::is_empty")]
pub settings_flags: HashMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub help: Option<CliHelpConfig>,
}
#[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>,
}
#[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 SinceTagConfig {
pub extensions: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub placeholder_pattern: Option<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>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub cleanup_paths: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LintConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub module_script: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub module_script: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuntimeConfig {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub runtime_type: Option<String>,
#[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<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>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dependencies: Option<Vec<String>>,
#[serde(rename = "playwrightBrowsers", skip_serializing_if = "Option::is_none")]
pub playwright_browsers: Option<Vec<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<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<HashMap<String, serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub command: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub builtin: Option<BuiltinAction>,
#[serde(skip_serializing_if = "Option::is_none")]
pub column: 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>,
}