browserflare 0.5.0

Rust client for Cloudflare Browser Rendering APIs
Documentation
use std::collections::HashSet;
use std::path::Path;

use chrono::Local;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};

use crate::config::JOBS_FILE;
use crate::error::Result;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Job {
    pub job_id: String,
    pub url: String,
    pub label: Option<String>,
    pub payload: Value,
    pub started_at: String,
    pub status: String,
    #[serde(flatten)]
    pub extra: Map<String, Value>,
}

pub fn load_jobs() -> Result<Vec<Job>> {
    let path = Path::new(JOBS_FILE);
    if !path.exists() {
        return Ok(Vec::new());
    }
    let content = std::fs::read_to_string(path)?;
    let jobs: Vec<Job> = serde_json::from_str(&content)?;
    Ok(jobs)
}

pub fn save_jobs(jobs: &[Job]) -> Result<()> {
    let json_str = serde_json::to_string_pretty(jobs)?;
    std::fs::write(JOBS_FILE, json_str)?;
    Ok(())
}

pub fn add_job(job_id: &str, url: &str, payload: &Value, label: Option<&str>) -> Result<Job> {
    let mut jobs = load_jobs()?;
    let job = Job {
        job_id: job_id.to_string(),
        url: url.to_string(),
        label: label.map(|s| s.to_string()),
        payload: payload.clone(),
        started_at: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
        status: "pending".to_string(),
        extra: Map::new(),
    };
    jobs.push(job.clone());
    save_jobs(&jobs)?;
    Ok(job)
}

pub fn update_job(job_id: &str, fields: Map<String, Value>) -> Result<()> {
    let mut jobs = load_jobs()?;
    for job in &mut jobs {
        if job.job_id == job_id {
            for (key, value) in &fields {
                match key.as_str() {
                    "status" => {
                        if let Some(s) = value.as_str() {
                            job.status = s.to_string();
                        }
                    }
                    "label" => {
                        job.label = value.as_str().map(|s| s.to_string());
                    }
                    _ => {
                        job.extra.insert(key.clone(), value.clone());
                    }
                }
            }
            break;
        }
    }
    save_jobs(&jobs)?;
    Ok(())
}

pub fn find_job(job_id: &str) -> Result<Option<Job>> {
    let jobs = load_jobs()?;
    Ok(jobs.into_iter().find(|j| j.job_id == job_id))
}

pub fn delete_jobs(job_ids: &[&str]) -> Result<()> {
    let ids: HashSet<&str> = job_ids.iter().copied().collect();
    let jobs = load_jobs()?;
    let filtered: Vec<Job> = jobs.into_iter().filter(|j| !ids.contains(j.job_id.as_str())).collect();
    save_jobs(&filtered)?;
    Ok(())
}

pub fn get_jobs_by_status(status: &str) -> Result<Vec<Job>> {
    let jobs = load_jobs()?;
    Ok(jobs.into_iter().filter(|j| j.status == status).collect())
}