poolster-core 0.5.0-alpha.1

Language-neutral OpenAPI code generation primitives
Documentation
use anyhow::{Result, bail};
use poolster_core::{engine::Contract, input::*};
use std::{
    path::Path,
    sync::{
        Arc,
        atomic::{AtomicUsize, Ordering},
    },
};

#[derive(Debug, PartialEq)]
struct Text(String);
impl Contract for Text {
    const NAME: &'static str = "test.text";
}
#[derive(Debug, PartialEq)]
struct Count(usize);
impl Contract for Count {
    const NAME: &'static str = "test.count";
}
fn summary(format: &str) -> InputSummary {
    InputSummary {
        format: format.into(),
        title: "Native ✓".into(),
        version: Some("1.0".into()),
        types: vec!["Example".into()],
        operations: vec![InputOperation {
            name: "read".into(),
            kind: "query".into(),
        }],
    }
}
struct Probe {
    id: String,
    format: String,
    calls: Arc<AtomicUsize>,
    fail: bool,
}
impl InputPlugin for Probe {
    fn id(&self) -> &str {
        &self.id
    }
    fn format(&self) -> &str {
        &self.format
    }
    fn load(&self, path: &Path) -> Result<InputContract> {
        self.calls.fetch_add(1, Ordering::SeqCst);
        if self.fail {
            bail!("native parser rejected line 7")
        }
        let mut input = InputContract::new(summary(&self.format));
        input.publish(Text(path.to_string_lossy().into_owned()))?;
        input.diagnostics.push(InputDiagnostic {
            code: "retained".into(),
            message: "source retained".into(),
        });
        Ok(input)
    }
}
fn probe(id: &str, format: &str, calls: &Arc<AtomicUsize>) -> Probe {
    Probe {
        id: id.into(),
        format: format.into(),
        calls: calls.clone(),
        fail: false,
    }
}

#[test]
fn multiple_native_capabilities_can_be_read_and_taken_independently() {
    let mut input = InputContract::new(summary("custom"));
    input.publish(Text("payload".into())).unwrap();
    input.publish(Count(3)).unwrap();
    assert_eq!(input.get::<Count>().unwrap(), &Count(3));
    assert_eq!(input.take::<Text>().unwrap(), Text("payload".into()));
    assert!(
        input
            .get::<Text>()
            .unwrap_err()
            .to_string()
            .contains("test.text")
    );
    assert_eq!(input.take::<Count>().unwrap(), Count(3));
    assert!(input.take::<Count>().is_err());
    input
        .publish(Text("replacement after take".into()))
        .unwrap();
    assert_eq!(input.get::<Text>().unwrap().0, "replacement after take");
}
#[test]
fn missing_take_does_not_remove_another_capability() {
    let mut input = InputContract::new(summary("custom"));
    input.publish(Text("unchanged".into())).unwrap();
    assert!(input.take::<Count>().is_err());
    assert_eq!(input.get::<Text>().unwrap().0, "unchanged");
}
#[test]
fn listing_is_sorted_and_has_no_parser_side_effects() {
    let calls = Arc::new(AtomicUsize::new(0));
    let mut registry = InputRegistry::new();
    for id in ["z.parser", "a.parser", "m.parser"] {
        registry.register(probe(id, "custom", &calls)).unwrap();
    }
    assert_eq!(
        registry
            .plugins()
            .iter()
            .map(|p| p.provider.as_str())
            .collect::<Vec<_>>(),
        ["a.parser", "m.parser", "z.parser"]
    );
    assert_eq!(calls.load(Ordering::SeqCst), 0);
}
#[test]
fn selection_errors_never_invoke_a_parser() {
    let calls = Arc::new(AtomicUsize::new(0));
    let mut registry = InputRegistry::new();
    registry
        .register(probe("a.parser", "custom", &calls))
        .unwrap();
    registry
        .register(probe("b.parser", "custom", &calls))
        .unwrap();
    for (format, provider) in [
        ("custom", None),
        ("missing", None),
        ("custom", Some("missing")),
        ("other", Some("a.parser")),
    ] {
        assert!(
            registry
                .load(format, provider, Path::new("unused"))
                .is_err()
        );
    }
    assert_eq!(calls.load(Ordering::SeqCst), 0);
}
#[test]
fn explicit_selection_invokes_only_the_chosen_provider_and_retains_metadata() {
    let first = Arc::new(AtomicUsize::new(0));
    let second = Arc::new(AtomicUsize::new(0));
    let mut registry = InputRegistry::new();
    registry
        .register(probe("a.parser", "custom", &first))
        .unwrap();
    registry
        .register(probe("b.parser", "custom", &second))
        .unwrap();
    let path = Path::new("dir with spaces/契約.custom");
    let loaded = registry.load("custom", Some("b.parser"), path).unwrap();
    assert_eq!(loaded.source, path);
    assert_eq!(loaded.provider, "b.parser");
    assert_eq!(loaded.contract.summary, summary("custom"));
    assert_eq!(loaded.contract.diagnostics[0].code, "retained");
    assert_eq!(first.load(Ordering::SeqCst), 0);
    assert_eq!(second.load(Ordering::SeqCst), 1);
}
#[test]
fn failed_registration_keeps_the_original_provider() {
    let calls = Arc::new(AtomicUsize::new(0));
    let replacement = Arc::new(AtomicUsize::new(0));
    let mut registry = InputRegistry::new();
    registry
        .register(probe("a.parser", "custom", &calls))
        .unwrap();
    assert!(
        registry
            .register(probe("a.parser", "other", &replacement))
            .is_err()
    );
    registry.load("custom", None, Path::new("unused")).unwrap();
    assert_eq!(registry.plugins().len(), 1);
    assert_eq!(calls.load(Ordering::SeqCst), 1);
    assert_eq!(replacement.load(Ordering::SeqCst), 0);
}
#[test]
fn invalid_provider_and_format_identifiers_are_rejected_without_registration() {
    let calls = Arc::new(AtomicUsize::new(0));
    for invalid in [
        "",
        "A",
        "9parser",
        " space",
        "a/b",
        "a:b",
        "a\n",
        "é.parser",
        "a🚀",
    ] {
        let mut registry = InputRegistry::new();
        assert!(
            registry.register(probe(invalid, "valid", &calls)).is_err(),
            "id {invalid:?}"
        );
        assert!(
            registry
                .register(probe("valid.parser", invalid, &calls))
                .is_err(),
            "format {invalid:?}"
        );
        assert!(registry.plugins().is_empty());
    }
    for valid in ["a", "a9", "a.b-c_d"] {
        InputRegistry::new()
            .register(probe(valid, valid, &calls))
            .unwrap();
    }
}
#[test]
fn loader_error_preserves_cause_provider_and_source() {
    let calls = Arc::new(AtomicUsize::new(0));
    let mut plugin = probe("custom.bad", "custom", &calls);
    plugin.fail = true;
    let mut registry = InputRegistry::new();
    registry.register(plugin).unwrap();
    let error = registry
        .load("custom", None, Path::new("broken.contract"))
        .err()
        .unwrap();
    let chain = format!("{error:#}");
    for expected in ["custom.bad", "broken.contract", "line 7"] {
        assert!(chain.contains(expected), "{chain}");
    }
}
#[test]
fn summary_and_diagnostics_roundtrip_json_without_losing_unicode_or_kinds() {
    let expected = summary("community.format-v2");
    let encoded = serde_json::to_string(&expected).unwrap();
    assert_eq!(
        serde_json::from_str::<InputSummary>(&encoded).unwrap(),
        expected
    );
    let diagnostic = InputDiagnostic {
        code: "unresolved".into(),
        message: "契約 unavailable".into(),
    };
    assert_eq!(
        serde_json::from_str::<InputDiagnostic>(&serde_json::to_string(&diagnostic).unwrap())
            .unwrap(),
        diagnostic
    );
}
#[test]
fn empty_registry_has_actionable_missing_provider_error() {
    let registry = InputRegistry::default();
    assert!(registry.plugins().is_empty());
    let error = registry
        .load("community", None, Path::new("unused"))
        .err()
        .unwrap();
    assert!(
        error
            .to_string()
            .contains("no input provider registered for format \"community\"")
    );
}