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}