use anyhow::{Context, Result};
use std::fs;
use std::path::{Path, PathBuf};
const PERMISSION_FILE_NAME: &str = "syft.pub.yaml";
pub const DEFAULT_RPC_PERMISSION_CONTENT: &str = r#"rules:
- pattern: '**/*.request'
access:
admin: []
read:
- '*'
write:
- '*'
- pattern: '**/*.response'
access:
admin: []
read:
- '*'
write:
- '*'
"#;
pub const DEFAULT_APP_PERMISSION_CONTENT: &str = r#"rules:
- pattern: "**"
access:
admin: []
read:
- "*"
write: []
"#;
#[derive(Debug, Clone)]
pub struct SyftBoxApp {
pub app_name: String,
pub email: String,
pub data_dir: PathBuf,
pub app_data_dir: PathBuf,
pub rpc_dir: PathBuf,
}
impl SyftBoxApp {
pub fn new(data_dir: &Path, email: &str, app_name: &str) -> Result<Self> {
let app_data_dir = data_dir
.join("datasites")
.join(email)
.join("app_data")
.join(app_name);
let rpc_dir = app_data_dir.join("rpc");
let app = Self {
app_name: app_name.to_string(),
email: email.to_string(),
data_dir: data_dir.to_path_buf(),
app_data_dir,
rpc_dir,
};
app.ensure_initialized()?;
Ok(app)
}
fn ensure_initialized(&self) -> Result<()> {
if !self.app_data_dir.exists() {
fs::create_dir_all(&self.app_data_dir).with_context(|| {
format!(
"Failed to create app_data directory: {:?}",
self.app_data_dir
)
})?;
println!("Created app_data directory: {:?}", self.app_data_dir);
}
let app_permission_file = self.app_data_dir.join(PERMISSION_FILE_NAME);
if !app_permission_file.exists() {
fs::write(&app_permission_file, DEFAULT_APP_PERMISSION_CONTENT).with_context(|| {
format!(
"Failed to create app permission file: {:?}",
app_permission_file
)
})?;
println!("Created app permission file: {:?}", app_permission_file);
}
if !self.rpc_dir.exists() {
fs::create_dir_all(&self.rpc_dir)
.with_context(|| format!("Failed to create RPC directory: {:?}", self.rpc_dir))?;
println!("Created RPC directory: {:?}", self.rpc_dir);
}
let rpc_permission_file = self.rpc_dir.join(PERMISSION_FILE_NAME);
if !rpc_permission_file.exists() {
fs::write(&rpc_permission_file, DEFAULT_RPC_PERMISSION_CONTENT).with_context(|| {
format!(
"Failed to create RPC permission file: {:?}",
rpc_permission_file
)
})?;
println!("Created RPC permission file: {:?}", rpc_permission_file);
}
Ok(())
}
pub fn endpoint_path(&self, endpoint_name: &str) -> PathBuf {
let clean_name = endpoint_name.trim_start_matches('/');
self.rpc_dir.join(clean_name)
}
pub fn register_endpoint(&self, endpoint_name: &str) -> Result<PathBuf> {
let endpoint_dir = self.endpoint_path(endpoint_name);
if !endpoint_dir.exists() {
fs::create_dir_all(&endpoint_dir).with_context(|| {
format!("Failed to create endpoint directory: {:?}", endpoint_dir)
})?;
println!("Registered endpoint: {}", endpoint_name);
}
Ok(endpoint_dir)
}
pub fn endpoint_exists(&self, endpoint_name: &str) -> bool {
self.endpoint_path(endpoint_name).exists()
}
pub fn list_endpoints(&self) -> Result<Vec<String>> {
let mut endpoints = Vec::new();
if self.rpc_dir.exists() {
for entry in fs::read_dir(&self.rpc_dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
if let Some(name) = path.file_name() {
if let Some(name_str) = name.to_str() {
endpoints.push(format!("/{}", name_str));
}
}
}
}
}
Ok(endpoints)
}
pub fn build_syft_url(&self, endpoint_name: &str) -> String {
let clean_endpoint = endpoint_name.trim_start_matches('/');
format!(
"syft://{}/app_data/{}/rpc/{}",
self.email, self.app_name, clean_endpoint
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_app_initialization() -> Result<()> {
let temp_dir = TempDir::new()?;
let app = SyftBoxApp::new(temp_dir.path(), "test@example.com", "test_app")?;
assert!(app.rpc_dir.exists());
let permission_file = app.rpc_dir.join(PERMISSION_FILE_NAME);
assert!(permission_file.exists());
Ok(())
}
#[test]
fn test_endpoint_registration() -> Result<()> {
let temp_dir = TempDir::new()?;
let app = SyftBoxApp::new(temp_dir.path(), "test@example.com", "test_app")?;
let endpoint_path = app.register_endpoint("/message")?;
assert!(endpoint_path.exists());
assert!(app.endpoint_exists("/message"));
let endpoints = app.list_endpoints()?;
assert!(endpoints.contains(&"/message".to_string()));
Ok(())
}
#[test]
fn test_syft_url_building() {
let temp_dir = TempDir::new().unwrap();
let app = SyftBoxApp::new(temp_dir.path(), "test@example.com", "test_app").unwrap();
let url = app.build_syft_url("/message");
assert_eq!(url, "syft://test@example.com/app_data/test_app/rpc/message");
}
}