use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct RegistryConfig {
pub default_registry: String,
pub auth: RegistryAuth,
}
#[derive(Debug, Clone)]
pub enum RegistryAuth {
Anonymous,
Basic { username: String, password: String },
}
impl Default for RegistryConfig {
fn default() -> Self {
Self {
default_registry: "ghcr.io/drasi-project".to_string(),
auth: RegistryAuth::Anonymous,
}
}
}
#[derive(Debug, Clone)]
pub struct HostVersionInfo {
pub sdk_version: String,
pub core_version: String,
pub lib_version: String,
pub target_triple: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResolvedPlugin {
pub reference: String,
pub version: String,
pub sdk_version: String,
pub core_version: String,
pub lib_version: String,
pub platform: String,
pub digest: String,
pub filename: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginMetadataJson {
pub name: String,
pub kind: String,
#[serde(rename = "type")]
pub plugin_type: String,
pub version: String,
pub sdk_version: String,
pub core_version: String,
pub lib_version: String,
pub target_triple: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub license: Option<String>,
}
#[derive(Debug, Clone)]
pub struct PluginReference {
pub registry: String,
pub repository: String,
pub tag: Option<String>,
}
impl PluginReference {
pub fn parse(reference: &str, default_registry: &str) -> anyhow::Result<Self> {
let (ref_without_tag, tag) = if let Some(at_pos) = reference.rfind('@') {
(
&reference[..at_pos],
Some(reference[at_pos + 1..].to_string()),
)
} else if let Some(colon_pos) = reference.rfind(':') {
let last_slash = reference.rfind('/').unwrap_or(0);
if colon_pos > last_slash {
(
&reference[..colon_pos],
Some(reference[colon_pos + 1..].to_string()),
)
} else {
(reference, None)
}
} else {
(reference, None)
};
let parts: Vec<&str> = ref_without_tag.splitn(2, '/').collect();
let (registry, repository) = if parts.len() == 2 && parts[0].contains('.') {
let first_slash = ref_without_tag
.find('/')
.expect("registry reference must contain '/'");
let registry = &ref_without_tag[..first_slash];
let repository = &ref_without_tag[first_slash + 1..];
(registry.to_string(), repository.to_string())
} else {
let (reg, ns) = if let Some(slash) = default_registry.find('/') {
(
default_registry[..slash].to_string(),
default_registry[slash + 1..].to_string(),
)
} else {
(default_registry.to_string(), String::new())
};
let repo = if ns.is_empty() {
ref_without_tag.to_string()
} else {
format!("{}/{}", ns, ref_without_tag)
};
(reg, repo)
};
Ok(Self {
registry,
repository,
tag,
})
}
pub fn to_oci_reference(&self) -> String {
match &self.tag {
Some(tag) => format!("{}/{}:{}", self.registry, self.repository, tag),
None => format!("{}/{}", self.registry, self.repository),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum PluginSourceKind {
Oci(String),
LocalDir(std::path::PathBuf),
}
impl PluginSourceKind {
pub fn parse(value: &str) -> Self {
if value.starts_with("file://") {
return Self::LocalDir(std::path::PathBuf::from(
value.strip_prefix("file://").unwrap_or(value),
));
}
if value.starts_with('/') {
return Self::LocalDir(std::path::PathBuf::from(value));
}
if value.starts_with("./")
|| value.starts_with("../")
|| value.starts_with(".\\")
|| value.starts_with("..\\")
{
return Self::LocalDir(std::path::PathBuf::from(value));
}
if value.starts_with('~') {
return Self::LocalDir(std::path::PathBuf::from(value));
}
if value.len() >= 3 {
let bytes = value.as_bytes();
if bytes[0].is_ascii_alphabetic()
&& bytes[1] == b':'
&& (bytes[2] == b'\\' || bytes[2] == b'/')
{
return Self::LocalDir(std::path::PathBuf::from(value));
}
}
if value.starts_with("\\\\") {
return Self::LocalDir(std::path::PathBuf::from(value));
}
Self::Oci(value.to_string())
}
pub fn is_local(&self) -> bool {
matches!(self, Self::LocalDir(_))
}
pub fn is_oci(&self) -> bool {
matches!(self, Self::Oci(_))
}
}
pub mod annotations {
pub const PLUGIN_KIND: &str = "io.drasi.plugin.kind";
pub const PLUGIN_TYPE: &str = "io.drasi.plugin.type";
pub const SDK_VERSION: &str = "io.drasi.plugin.sdk-version";
pub const CORE_VERSION: &str = "io.drasi.plugin.core-version";
pub const LIB_VERSION: &str = "io.drasi.plugin.lib-version";
pub const TARGET_TRIPLE: &str = "io.drasi.plugin.target-triple";
}
pub mod media_types {
pub const PLUGIN_BINARY: &str = "application/vnd.drasi.plugin.v1+binary";
pub const PLUGIN_METADATA: &str = "application/vnd.drasi.plugin.v1+metadata";
pub const PLUGIN_CONFIG: &str = "application/vnd.drasi.plugin.v1+config";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_full_reference() {
let p = PluginReference::parse(
"ghcr.io/drasi-project/source/postgres:0.1.8",
"ghcr.io/drasi-project",
)
.unwrap();
assert_eq!(p.registry, "ghcr.io");
assert_eq!(p.repository, "drasi-project/source/postgres");
assert_eq!(p.tag, Some("0.1.8".to_string()));
}
#[test]
fn test_parse_short_reference() {
let p = PluginReference::parse("source/postgres:0.1.8", "ghcr.io/drasi-project").unwrap();
assert_eq!(p.registry, "ghcr.io");
assert_eq!(p.repository, "drasi-project/source/postgres");
assert_eq!(p.tag, Some("0.1.8".to_string()));
}
#[test]
fn test_parse_no_tag() {
let p = PluginReference::parse("source/postgres", "ghcr.io/drasi-project").unwrap();
assert_eq!(p.registry, "ghcr.io");
assert_eq!(p.repository, "drasi-project/source/postgres");
assert_eq!(p.tag, None);
}
#[test]
fn test_parse_third_party() {
let p = PluginReference::parse(
"ghcr.io/acme-corp/custom-source:1.0.0",
"ghcr.io/drasi-project",
)
.unwrap();
assert_eq!(p.registry, "ghcr.io");
assert_eq!(p.repository, "acme-corp/custom-source");
assert_eq!(p.tag, Some("1.0.0".to_string()));
}
#[test]
fn test_to_oci_reference() {
let p = PluginReference::parse("source/postgres:0.1.8", "ghcr.io/drasi-project").unwrap();
assert_eq!(
p.to_oci_reference(),
"ghcr.io/drasi-project/source/postgres:0.1.8"
);
}
#[test]
fn test_to_oci_reference_no_tag() {
let p = PluginReference::parse("source/postgres", "ghcr.io/drasi-project").unwrap();
assert_eq!(
p.to_oci_reference(),
"ghcr.io/drasi-project/source/postgres"
);
}
#[test]
fn test_source_kind_oci_urls() {
assert_eq!(
PluginSourceKind::parse("ghcr.io/drasi-project"),
PluginSourceKind::Oci("ghcr.io/drasi-project".to_string())
);
assert_eq!(
PluginSourceKind::parse("registry.example.com/plugins"),
PluginSourceKind::Oci("registry.example.com/plugins".to_string())
);
assert!(PluginSourceKind::parse("ghcr.io/drasi-project").is_oci());
assert!(!PluginSourceKind::parse("ghcr.io/drasi-project").is_local());
}
#[test]
fn test_source_kind_unix_absolute() {
assert_eq!(
PluginSourceKind::parse("/opt/plugins"),
PluginSourceKind::LocalDir(std::path::PathBuf::from("/opt/plugins"))
);
assert_eq!(
PluginSourceKind::parse("/home/user/plugins"),
PluginSourceKind::LocalDir(std::path::PathBuf::from("/home/user/plugins"))
);
assert!(PluginSourceKind::parse("/opt/plugins").is_local());
}
#[test]
fn test_source_kind_relative_paths() {
assert_eq!(
PluginSourceKind::parse("./plugins"),
PluginSourceKind::LocalDir(std::path::PathBuf::from("./plugins"))
);
assert_eq!(
PluginSourceKind::parse("../drasi-core/target/debug/plugins"),
PluginSourceKind::LocalDir(std::path::PathBuf::from(
"../drasi-core/target/debug/plugins"
))
);
}
#[test]
fn test_source_kind_windows_relative() {
assert_eq!(
PluginSourceKind::parse(".\\plugins"),
PluginSourceKind::LocalDir(std::path::PathBuf::from(".\\plugins"))
);
assert_eq!(
PluginSourceKind::parse("..\\plugins"),
PluginSourceKind::LocalDir(std::path::PathBuf::from("..\\plugins"))
);
}
#[test]
fn test_source_kind_home_relative() {
assert_eq!(
PluginSourceKind::parse("~/plugins"),
PluginSourceKind::LocalDir(std::path::PathBuf::from("~/plugins"))
);
}
#[test]
fn test_source_kind_file_uri() {
assert_eq!(
PluginSourceKind::parse("file:///opt/plugins"),
PluginSourceKind::LocalDir(std::path::PathBuf::from("/opt/plugins"))
);
assert_eq!(
PluginSourceKind::parse("file://C:/plugins"),
PluginSourceKind::LocalDir(std::path::PathBuf::from("C:/plugins"))
);
}
#[test]
fn test_source_kind_windows_drive() {
assert_eq!(
PluginSourceKind::parse("C:\\plugins"),
PluginSourceKind::LocalDir(std::path::PathBuf::from("C:\\plugins"))
);
assert_eq!(
PluginSourceKind::parse("D:/plugins"),
PluginSourceKind::LocalDir(std::path::PathBuf::from("D:/plugins"))
);
}
#[test]
fn test_source_kind_windows_unc() {
assert_eq!(
PluginSourceKind::parse("\\\\server\\share\\plugins"),
PluginSourceKind::LocalDir(std::path::PathBuf::from("\\\\server\\share\\plugins"))
);
}
}