Skip to main content

asimov_runner/programs/
reasoner.rs

1// This is free and unencumbered software released into the public domain.
2
3//! RDF dataset entailment through an external reasoner program.
4
5use crate::{CommandExt, Executor, ExecutorError, GraphInput, GraphOutput, JsonlStream};
6use alloc::boxed::Box;
7use async_trait::async_trait;
8use derive_more::Debug;
9use std::{ffi::OsStr, process::Stdio};
10
11pub use asimov_patterns::ReasonerOptions;
12
13/// A live JSONL graph stream, or an error starting the reasoner.
14pub type ReasonerResult = Result<JsonlStream, ExecutorError>;
15
16/// An external [reasoner] that consumes an RDF dataset and emits entailed RDF.
17///
18/// Inference rules and the relationship between input and output graphs are
19/// determined by the external program. This wrapper transports JSONL lines using
20/// the concurrent streaming behavior described in [`crate::programs`].
21///
22/// [reasoner]: https://asimov-specs.github.io/program-patterns/#reasoner
23#[allow(unused)]
24#[derive(Debug)]
25pub struct Reasoner {
26    executor: Executor,
27    options: ReasonerOptions,
28    input: GraphInput,
29    output: GraphOutput,
30}
31
32impl Reasoner {
33    /// Configures a reasoner without starting it.
34    ///
35    /// Adds any configured `--input=<format>` and `--output=<format>` arguments,
36    /// followed by `options.other`. The input and output values select stdin
37    /// and stdout; stderr is captured for failure diagnostics.
38    /// Byte input is lazily adapted into JSONL batches using [`GraphInput::into_jsonl`].
39    pub fn new(
40        program: impl AsRef<OsStr>,
41        input: GraphInput,
42        output: GraphOutput,
43        options: ReasonerOptions,
44    ) -> Self {
45        let mut executor = Executor::new(program);
46        executor
47            .command()
48            .option("input", options.input.as_ref())
49            .option("output", options.output.as_ref())
50            .args(&options.other)
51            .stdin(input.as_stdio())
52            .stdout(output.as_stdio())
53            .stderr(Stdio::piped());
54
55        Self {
56            executor,
57            options,
58            input: input.into_jsonl(),
59            output,
60        }
61    }
62
63    /// Starts a child and returns its live JSONL inference stream.
64    ///
65    /// After successful spawning, input ownership moves into the stream, which
66    /// feeds it concurrently when polled. Subsequent executions have no graph input.
67    ///
68    /// # Errors
69    ///
70    /// Spawn failures are returned directly; input, output, wait, and exit failures are
71    /// stream items. Consume the stream to completion to check process success.
72    pub async fn execute(&mut self) -> ReasonerResult {
73        self.executor
74            .execute_jsonl_with_io(&mut self.input, &mut self.output)
75            .await
76    }
77}
78
79impl asimov_patterns::Reasoner<JsonlStream> for Reasoner {}
80
81crate::batch::with_batching!(Reasoner);
82
83crate::pipeline::stage!(
84    Reasoner,
85    value,
86    value.input,
87    value.output,
88    value.options.input.as_deref(),
89    value.options.output.as_deref()
90);
91
92#[async_trait]
93impl asimov_patterns::Execute<JsonlStream> for Reasoner {
94    type Error = ExecutorError;
95
96    async fn execute(&mut self) -> ReasonerResult {
97        self.execute().await
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    //use super::*;
104    //use asimov_patterns::Execute;
105
106    #[tokio::test]
107    async fn test_execute() {
108        // TODO
109    }
110}