fallow-api 3.15.0

Programmatic API contract types for fallow
Documentation
use std::env;
use std::fs;
use std::io;
use std::path::PathBuf;

use serde::Deserialize;

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct ProtocolManifest {
    schema_version: u32,
    wire_protocol_version: u32,
    semantic_schema_version: u32,
    analysis_operation: String,
    status_operation: String,
    query_operations: Vec<String>,
    session_envelope_types: Vec<String>,
    backend: Backend,
    sidecar: Sidecar,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Backend {
    family: String,
    version: String,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Sidecar {
    package: String,
    version_source: String,
}

fn rust_string(value: &str) -> String {
    format!("{value:?}")
}

fn rust_string_slice(values: &[String]) -> String {
    values
        .iter()
        .map(|value| rust_string(value))
        .collect::<Vec<_>>()
        .join(", ")
}

fn invalid_manifest(message: &'static str) -> io::Error {
    io::Error::new(io::ErrorKind::InvalidData, message)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    const MANIFEST_PATH: &str = "type-aware-protocol.json";
    println!("cargo:rerun-if-changed={MANIFEST_PATH}");

    let source = fs::read_to_string(MANIFEST_PATH)?;
    let manifest: ProtocolManifest = serde_json::from_str(&source)?;
    if manifest.schema_version != 1 {
        return Err(invalid_manifest("unsupported manifest schema").into());
    }
    if manifest.wire_protocol_version == 0 || manifest.semantic_schema_version == 0 {
        return Err(
            invalid_manifest("protocol and semantic schema versions must be positive").into(),
        );
    }
    if manifest.query_operations.is_empty() {
        return Err(invalid_manifest("query operations must not be empty").into());
    }
    if manifest.sidecar.version_source != "workspace-package" {
        return Err(invalid_manifest("unsupported sidecar version source").into());
    }

    let generated = format!(
        "// Generated from crates/api/type-aware-protocol.json. Do not edit.\n\
         pub(super) const WIRE_PROTOCOL_VERSION: u32 = {};\n\
         pub(super) const SEMANTIC_SCHEMA_VERSION: u32 = {};\n\
         pub(super) const ANALYSIS_OPERATION: &str = {};\n\
         pub(super) const STATUS_OPERATION: &str = {};\n\
         pub(super) const QUERY_OPERATIONS: &[&str] = &[{}];\n\
         pub(super) const SESSION_ENVELOPE_TYPES: &[&str] = &[{}];\n\
         pub(super) const BACKEND_FAMILY: &str = {};\n\
         pub(super) const BACKEND_VERSION: &str = {};\n\
         pub(super) const SIDECAR_PACKAGE: &str = {};\n",
        manifest.wire_protocol_version,
        manifest.semantic_schema_version,
        rust_string(&manifest.analysis_operation),
        rust_string(&manifest.status_operation),
        rust_string_slice(&manifest.query_operations),
        rust_string_slice(&manifest.session_envelope_types),
        rust_string(&manifest.backend.family),
        rust_string(&manifest.backend.version),
        rust_string(&manifest.sidecar.package),
    );
    let output = PathBuf::from(
        env::var_os("OUT_DIR")
            .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "OUT_DIR is not set"))?,
    );
    fs::write(output.join("type_aware_protocol.rs"), generated)?;
    Ok(())
}