gunnar-server 3.0.0

The gunnar git server: SSH and smart-HTTP git transports and the gRPC control plane. The appliance half, extracted from gunnar-cli so the server code lives in its own crate.
Documentation
//! RA2-11: a doc comment that names an `Admin` RPC must head THAT RPC's function.
//!
//! The settings change once slid two docs down one function: `Admin.AuditTrail`'s
//! doc ended up on `get_setting` and the control plane's audit-trail doc on
//! `setting()`, leaving `audit_trail` with none on either side. rustdoc cannot
//! catch that (any doc on any fn is valid), so this reads the two files and
//! checks each heading against the fn it heads.
//!
//! A heading is the FIRST line of a `///` block, written either
//! ``**`Admin.Foo`**`` (the gRPC service) or ``**…** (`Admin.Foo`…`` (the control
//! plane). Its RPC name in snake case must be the name of the `fn` the block heads,
//! and a block holds at most one heading.

use std::path::Path;

/// The RPC a heading line names, if the line is a heading.
fn heading_rpc(line: &str) -> Option<String> {
    let line = line.trim_start().strip_prefix("///")?.trim();
    let rest = if let Some(r) = line.strip_prefix("**`Admin.") {
        r
    } else {
        let at = line.find("** (`Admin.")?;
        if !line.starts_with("**") {
            return None;
        }
        &line[at + "** (`Admin.".len()..]
    };
    let name: String = rest
        .chars()
        .take_while(|c| c.is_ascii_alphanumeric())
        .collect();
    (!name.is_empty()).then_some(name)
}

fn snake(camel: &str) -> String {
    let mut out = String::new();
    for (i, c) in camel.chars().enumerate() {
        if c.is_ascii_uppercase() {
            if i > 0 {
                out.push('_');
            }
            out.push(c.to_ascii_lowercase());
        } else {
            out.push(c);
        }
    }
    out
}

/// The name of the `fn` declared on this line, if any.
fn fn_name(line: &str) -> Option<String> {
    let at = line.find("fn ")?;
    let before = line[..at].trim();
    if !before
        .split_whitespace()
        .all(|w| matches!(w, "pub" | "pub(crate)" | "async" | "const" | "unsafe"))
    {
        return None;
    }
    let name: String = line[at + 3..]
        .chars()
        .take_while(|c| c.is_ascii_alphanumeric() || *c == '_')
        .collect();
    (!name.is_empty()).then_some(name)
}

/// Every (heading RPC, fn it heads) pair in `src`, and every block with two headings.
fn pairs(src: &str) -> (Vec<(String, String)>, Vec<String>) {
    let lines: Vec<&str> = src.lines().collect();
    let (mut out, mut doubled) = (Vec::new(), Vec::new());
    let mut i = 0;
    while i < lines.len() {
        if !lines[i].trim_start().starts_with("///") {
            i += 1;
            continue;
        }
        let start = i;
        while i < lines.len() && lines[i].trim_start().starts_with("///") {
            i += 1;
        }
        let headings: Vec<String> = lines[start..i]
            .iter()
            .filter_map(|l| heading_rpc(l))
            .collect();
        if headings.len() > 1 {
            doubled.push(format!("line {}: {headings:?}", start + 1));
        }
        let Some(first) = heading_rpc(lines[start]) else {
            continue;
        };
        // Skip attributes between the doc and its item.
        let mut j = i;
        while j < lines.len() && lines[j].trim_start().starts_with("#[") {
            j += 1;
        }
        let headed = lines
            .get(j)
            .and_then(|l| fn_name(l))
            .unwrap_or_else(|| "(not a fn)".to_owned());
        out.push((first, headed));
    }
    (out, doubled)
}

fn check(rel: &str, must_have: &[&str]) {
    let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(rel);
    let src =
        std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
    let (pairs, doubled) = pairs(&src);
    assert!(
        doubled.is_empty(),
        "{rel}: a doc block names more than one Admin RPC: {doubled:?}"
    );
    let wrong: Vec<_> = pairs.iter().filter(|(rpc, f)| snake(rpc) != *f).collect();
    assert!(
        wrong.is_empty(),
        "{rel}: an `Admin.X` doc heads the wrong fn (rpc, fn): {wrong:?}"
    );
    for rpc in must_have {
        assert!(
            pairs.iter().any(|(r, f)| r == rpc && *f == snake(rpc)),
            "{rel}: `{}` has no doc naming `Admin.{rpc}`",
            snake(rpc)
        );
    }
}

#[test]
fn the_grpc_service_docs_head_their_rpcs() {
    check(
        "../gunnar-grpc/src/service/admin.rs",
        &["AuditTrail", "GetSetting", "SetSetting", "Topology"],
    );
}

#[test]
fn the_control_plane_docs_head_their_rpcs() {
    check("src/control_plane.rs", &["AuditTrail", "IssuerInfo"]);
}

#[test]
fn the_reader_sees_both_heading_forms() {
    assert_eq!(
        heading_rpc("    /// **`Admin.AuditTrail`** (lane T11): x").as_deref(),
        Some("AuditTrail")
    );
    assert_eq!(
        heading_rpc("    /// **The chain** (`Admin.AuditTrail`, lane T11).").as_deref(),
        Some("AuditTrail")
    );
    assert_eq!(
        heading_rpc("    /// **What replication is doing**, for `Admin.Stats`."),
        None
    );
    assert_eq!(snake("GetSetting"), "get_setting");
    assert_eq!(
        fn_name("    async fn audit_trail(").as_deref(),
        Some("audit_trail")
    );
    assert_eq!(
        fn_name("    pub(crate) fn setting(&self"),
        Some("setting".to_owned())
    );
    let (p, d) = pairs(
        "/// **`Admin.AuditTrail`** a\n/// **`Admin.GetSetting`** b\nasync fn get_setting(\n",
    );
    assert_eq!(p, vec![("AuditTrail".to_owned(), "get_setting".to_owned())]);
    assert_eq!(d.len(), 1);
}