flarer 0.1.0

Rust client and CLI for Cloudflare's Browser Rendering REST API (content, screenshot, PDF, snapshot, markdown, scrape, JSON extraction, links, crawl).
Documentation
//! Request/response types and helpers.

use std::path::PathBuf;

use serde::{Deserialize, Serialize};
use serde_json::Value;

/// `response_format` payload accepted by Cloudflare's JSON tool.
///
/// The schema is held as a free-form [`serde_json::Value`] so it round-trips
/// with the canonical JSON Schema representation (keys like `type`,
/// `properties`, `required`, ...).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseFormat {
    /// Always `"json_schema"` for Cloudflare today.
    #[serde(rename = "type")]
    pub format_type: String,
    /// The JSON Schema describing the expected response shape.
    pub schema: Value,
}

impl ResponseFormat {
    /// Convenience constructor for `{"type": "json_schema", "schema": ...}`.
    pub fn json_schema(schema: Value) -> Self {
        Self {
            format_type: "json_schema".to_string(),
            schema,
        }
    }
}

/// Optional knobs passed to [`Flarer::json`](crate::Flarer::json).
#[derive(Debug, Clone, Default)]
pub struct JsonOptions {
    /// Free-form prompt steering the LLM extractor.
    pub prompt: Option<String>,
    /// Structured output schema.
    pub response_format: Option<ResponseFormat>,
}

impl JsonOptions {
    /// New, empty options.
    pub fn new() -> Self {
        Self::default()
    }
    /// Set the prompt.
    pub fn with_prompt(mut self, prompt: impl Into<String>) -> Self {
        self.prompt = Some(prompt.into());
        self
    }
    /// Set the response format.
    pub fn with_response_format(mut self, rf: ResponseFormat) -> Self {
        self.response_format = Some(rf);
        self
    }
}

/// Decoded `snapshot` endpoint response.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SnapshotResult {
    /// Base64-encoded PNG screenshot.
    pub screenshot: String,
    /// Captured HTML content.
    pub content: String,
}

/// Top-level envelope returned by the Cloudflare API.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CfEnvelope<T> {
    /// Whether the request succeeded.
    pub success: bool,
    /// Result body when `success == true`.
    pub result: Option<T>,
    /// Errors when `success == false`.
    #[serde(default)]
    pub errors: Vec<Value>,
    /// Informational messages.
    #[serde(default)]
    pub messages: Vec<Value>,
}

/// Result of running a tool. The variant depends on the tool invoked.
#[derive(Debug, Clone)]
pub enum Output {
    /// Free-form JSON value (content, markdown, scrape, links, crawl, json).
    Json(Value),
    /// Path to a binary file written to disk (screenshot, pdf).
    File(PathBuf),
    /// Snapshot result: PNG path + captured HTML.
    Snapshot {
        /// Path to the saved PNG.
        png: PathBuf,
        /// HTML content captured by the snapshot.
        html: String,
    },
}

impl Output {
    /// Pretty-print the output for human consumption.
    pub fn display_string(&self) -> String {
        match self {
            Output::Json(v) => serde_json::to_string_pretty(v).unwrap_or_else(|_| v.to_string()),
            Output::File(p) => format!("Saved to {}", p.display()),
            Output::Snapshot { png, html } => {
                format!("Snapshot PNG: {}\n--- HTML ---\n{}", png.display(), html)
            }
        }
    }
}