orbexa 0.0.4

Rust CLI for applying Codexa-generated Notion artifacts to managed Notion data sources and pages.
Documentation
use serde::{Deserialize, Serialize};
use std::{
    fs, io,
    path::{Path, PathBuf},
};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NotionManifest {
    pub schema: String,
    pub producer: String,
    pub producer_version: String,
    pub pages: Vec<NotionManifestPage>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NotionManifestPage {
    pub document_id: String,
    pub root: String,
    pub path: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NotionPageArtifact {
    pub schema: String,
    pub producer: String,
    pub producer_version: String,
    pub document: ArtifactDocument,
    pub navigation: ArtifactNavigation,
    pub source: ArtifactSource,
    pub target: ArtifactTarget,
    pub web: Option<ArtifactWeb>,
    pub links: Vec<ArtifactLink>,
    pub content: ArtifactContent,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArtifactDocument {
    pub schema: String,
    pub id: String,
    pub title: String,
    pub description: String,
    pub kind: String,
    pub status: String,
    pub visibility: String,
    pub tags: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArtifactNavigation {
    pub root: String,
    pub product: String,
    pub section: String,
    pub order: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArtifactSource {
    pub repository: String,
    pub path: String,
    pub commit: String,
    pub content_hash: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArtifactTarget {
    pub workspace: String,
    pub root: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArtifactWeb {
    pub slug: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArtifactLink {
    pub raw: String,
    pub target_id: String,
    pub heading: Option<String>,
    pub label: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArtifactContent {
    pub format: String,
    pub markdown: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoadedManifest {
    pub path: PathBuf,
    pub manifest: NotionManifest,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoadedPageArtifact {
    pub path: PathBuf,
    pub artifact: NotionPageArtifact,
}

pub fn load_manifest(input_dir: impl AsRef<Path>) -> Result<LoadedManifest, ArtifactError> {
    let path = input_dir.as_ref().join("manifest.json");
    let manifest: NotionManifest = serde_json::from_str(&fs::read_to_string(&path)?)?;
    if manifest.schema != "codexa.notion.manifest@2" {
        return Err(ArtifactError::InvalidSchema(manifest.schema));
    }
    Ok(LoadedManifest { path, manifest })
}
pub fn load_page_artifact(
    input_dir: impl AsRef<Path>,
    page_path: &str,
) -> Result<LoadedPageArtifact, ArtifactError> {
    let path = input_dir.as_ref().join(page_path);
    let artifact: NotionPageArtifact = serde_json::from_str(&fs::read_to_string(&path)?)?;
    if artifact.schema != "codexa.notion.page@2" {
        return Err(ArtifactError::InvalidSchema(artifact.schema));
    }
    Ok(LoadedPageArtifact { path, artifact })
}

#[derive(Debug)]
pub enum ArtifactError {
    Io(io::Error),
    Json(serde_json::Error),
    InvalidSchema(String),
}
impl std::fmt::Display for ArtifactError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Io(e) => write!(f, "artifact I/O error: {e}"),
            Self::Json(e) => write!(f, "artifact JSON error: {e}"),
            Self::InvalidSchema(s) => write!(f, "unsupported artifact schema `{s}`"),
        }
    }
}
impl std::error::Error for ArtifactError {}
impl From<io::Error> for ArtifactError {
    fn from(v: io::Error) -> Self {
        Self::Io(v)
    }
}
impl From<serde_json::Error> for ArtifactError {
    fn from(v: serde_json::Error) -> Self {
        Self::Json(v)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn parses_page_v2() {
        let json = r##"{"schema":"codexa.notion.page@2","producer":"codexa","producer_version":"0.0.2","document":{"schema":"codexa.document@2","id":"codexa.index","title":"Codexa","description":"Docs","kind":"index","status":"active","visibility":"public","tags":[]},"navigation":{"root":"docs","product":"codexa","section":"Overview","order":0},"source":{"repository":"orbyts/codexa","path":"docs/index.md","commit":"abc","content_hash":"sha256:abc"},"target":{"workspace":"codexa","root":"docs"},"web":{"slug":"/docs/codexa"},"links":[],"content":{"format":"markdown","markdown":"# Codexa"}}"##;
        let artifact: NotionPageArtifact = serde_json::from_str(json).unwrap();
        assert_eq!(artifact.target.root, "docs");
    }
}