| use crate::capability::Capabilities; |
| use crate::error::BexError; |
|
|
| #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] |
| pub struct Manifest { |
| pub schema: u32, |
| pub id: String, |
| pub name: String, |
| pub version: String, |
| pub authors: Vec<String>, |
| pub abi: String, |
| pub provides: ProvidesSpec, |
| pub network: NetworkSpec, |
| pub storage: bool, |
| #[serde(default)] |
| pub secrets: Vec<String>, |
| |
| #[serde(default)] |
| pub allow_js: bool, |
| |
| #[serde(default)] |
| pub allow_js_fetch: bool, |
| pub display: DisplaySpec, |
| } |
|
|
| #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] |
| pub struct ProvidesSpec { |
| #[serde(default)] pub home: bool, |
| #[serde(default)] pub category: bool, |
| #[serde(default)] pub search: bool, |
| #[serde(default)] pub info: bool, |
| #[serde(default)] pub servers: bool, |
| #[serde(default)] pub stream: bool, |
| #[serde(default)] pub subtitles: bool, |
| #[serde(default)] pub articles: bool, |
| } |
|
|
| #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] |
| pub struct NetworkSpec { |
| #[serde(default = "default_hosts")] pub hosts: Vec<String>, |
| #[serde(default = "default_concurrent")] pub concurrent: usize, |
| } |
|
|
| #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] |
| pub struct DisplaySpec { |
| pub icon: Option<String>, |
| pub description: Option<String>, |
| #[serde(default)] pub tags: Vec<String>, |
| #[serde(default = "default_priority")] pub priority: u32, |
| #[serde(default)] pub adult: bool, |
| } |
|
|
| fn default_hosts() -> Vec<String> { vec!["*".into()] } |
| fn default_concurrent() -> usize { 8 } |
| fn default_priority() -> u32 { 100 } |
|
|
| impl Manifest { |
| pub fn validate(&self, host_version: &str) -> Result<(), BexError> { |
| if self.schema != 1 { |
| return Err(BexError::ManifestInvalid(format!("unsupported schema: {}", self.schema))); |
| } |
| if !self.id.contains('.') || self.id.len() < 5 { |
| return Err(BexError::ManifestInvalid(format!("id '{}' must be reverse-DNS", self.id))); |
| } |
| let req = semver::VersionReq::parse(&self.abi) |
| .map_err(|e| BexError::ManifestInvalid(format!("abi: {e}")))?; |
| let host = semver::Version::parse(host_version) |
| .map_err(|e| BexError::ManifestInvalid(format!("host: {e}")))?; |
| if !req.matches(&host) { |
| return Err(BexError::AbiMismatch { host: host_version.into(), plugin_requires: self.abi.clone() }); |
| } |
| Ok(()) |
| } |
|
|
| pub fn allows_host(&self, host: &str) -> bool { |
| self.network.hosts.iter().any(|p| { |
| if p == "*" { return true; } |
| if let Some(suffix) = p.strip_prefix("*.") { |
| host.ends_with(&format!(".{suffix}")) || host == suffix |
| } else { host == p } |
| }) |
| } |
|
|
| pub fn capabilities(&self) -> Capabilities { Capabilities::from_manifest(&self.provides) } |
| } |
|
|