Skip to main content

rvoip_harness/
lib.rs

1//! # rvoip-harness
2//!
3//! Re-exports the provider trait surface defined in
4//! [`rvoip_core_traits::harness`] (post-V2.A) and supplies no-op
5//! default implementations useful for tests + harness-disabled
6//! builds.
7//!
8//! Per `rvoip-core/INTERFACE_DESIGN.md` §2.1 the trait shapes live in
9//! `rvoip-core-traits` (so both the Orchestrator and external
10//! provider crates can depend on them without cycling), and concrete
11//! provider crates depend on this re-export crate.
12
13pub use rvoip_core_traits::harness::*;
14
15use async_trait::async_trait;
16use rvoip_core_traits::error::Result;
17use rvoip_core_traits::ids::ConnectionId;
18use rvoip_core_traits::stream::MediaFrame;
19use std::sync::Mutex;
20
21pub struct NoOpAsrProvider;
22pub struct NoOpAsrStream;
23
24#[async_trait]
25impl AsrProvider for NoOpAsrProvider {
26    async fn open_stream(
27        &self,
28        _conn: ConnectionId,
29        _config: AsrConfig,
30    ) -> Result<Box<dyn AsrStream>> {
31        Ok(Box::new(NoOpAsrStream))
32    }
33}
34
35#[async_trait]
36impl AsrStream for NoOpAsrStream {
37    async fn push(&self, _frame: MediaFrame) -> Result<()> {
38        Ok(())
39    }
40    async fn next(&self) -> Option<AsrResult> {
41        None
42    }
43    async fn close(&self) -> Result<()> {
44        Ok(())
45    }
46}
47
48pub struct NoOpTtsProvider;
49pub struct NoOpTtsPlayback;
50
51#[async_trait]
52impl TtsProvider for NoOpTtsProvider {
53    async fn synthesize(&self, _request: TtsRequest) -> Result<Box<dyn TtsPlayback>> {
54        Ok(Box::new(NoOpTtsPlayback))
55    }
56}
57
58#[async_trait]
59impl TtsPlayback for NoOpTtsPlayback {
60    async fn next_frame(&self) -> Option<MediaFrame> {
61        None
62    }
63    async fn cancel(&self) -> Result<()> {
64        Ok(())
65    }
66}
67
68pub struct ListenOnlyDialog;
69
70#[async_trait]
71impl DialogManager for ListenOnlyDialog {
72    async fn turn(&self, _t: &AsrResult) -> Result<DialogAction> {
73        Ok(DialogAction::Listen)
74    }
75}
76
77pub struct VecRecordingSink {
78    inner: Mutex<Vec<u8>>,
79    url: String,
80}
81
82impl VecRecordingSink {
83    pub fn new(url: impl Into<String>) -> Self {
84        Self {
85            inner: Mutex::new(Vec::new()),
86            url: url.into(),
87        }
88    }
89
90    pub fn bytes(&self) -> Vec<u8> {
91        self.inner
92            .lock()
93            .expect("vec recording sink lock poisoned")
94            .clone()
95    }
96}
97
98#[async_trait]
99impl RecordingSink for VecRecordingSink {
100    async fn write(&self, frame: MediaFrame) -> Result<()> {
101        self.inner
102            .lock()
103            .expect("vec recording sink lock poisoned")
104            .extend_from_slice(&frame.payload);
105        Ok(())
106    }
107    async fn close(&self) -> Result<RecordingArtifact> {
108        let g = self.inner.lock().expect("vec recording sink lock poisoned");
109        Ok(RecordingArtifact {
110            url: self.url.clone(),
111            bytes_written: g.len() as u64,
112            duration_ms: 0,
113            content_hash: String::new(),
114        })
115    }
116}