Skip to main content

golem_schema/schema/
stream.rs

1// Copyright 2024-2026 Golem Cloud
2//
3// Licensed under the Golem Source License v1.1 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://license.golem.cloud/LICENSE
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Affine native streams carried recursively by [`super::SchemaValue`].
16
17use crate::schema::conversion::{FromSchema, FromSchemaError, IntoSchema, SchemaBuilder};
18use crate::schema::metadata::{MetadataEnvelope, TypeId};
19use crate::schema::schema_type::SchemaType;
20use crate::schema::schema_value::SchemaValue;
21
22#[cfg(all(feature = "guest", not(feature = "host")))]
23type RawSchemaValueStream = wit_bindgen::StreamReader<super::wit::wire::SchemaValueTree>;
24
25#[cfg(all(
26    any(feature = "host", feature = "native-stream"),
27    not(feature = "guest")
28))]
29mod active {
30    use std::any::Any;
31    use std::sync::{Arc, Mutex};
32
33    type OpaqueEndpoint = Box<dyn Any + Send>;
34
35    /// Host-side stream leaf. Clones share one take-once endpoint; they never
36    /// duplicate a live stream.
37    ///
38    /// The endpoint is deliberately type-erased here. The schema model owns
39    /// affine transfer semantics, while the embedding runtime owns the relay
40    /// protocol used to connect Store-local Component Model streams.
41    #[derive(Clone)]
42    pub struct SchemaValueStream {
43        inner: Arc<Mutex<Option<OpaqueEndpoint>>>,
44    }
45
46    impl SchemaValueStream {
47        #[doc(hidden)]
48        pub fn from_host_endpoint(endpoint: impl Any + Send) -> Self {
49            Self {
50                inner: Arc::new(Mutex::new(Some(Box::new(endpoint)))),
51            }
52        }
53
54        #[doc(hidden)]
55        pub fn take_host_endpoint<T: Any + Send>(&self) -> Result<T, String> {
56            let endpoint = self
57                .inner
58                .lock()
59                .expect("schema value stream mutex poisoned")
60                .take()
61                .ok_or_else(|| "schema value stream was already transferred".to_string())?;
62            endpoint
63                .downcast::<T>()
64                .map(|endpoint| *endpoint)
65                .map_err(|_| {
66                    "schema value stream endpoint belongs to an incompatible runtime".to_string()
67                })
68        }
69
70        #[doc(hidden)]
71        pub fn with_host_endpoint<T: Any + Send, R>(
72            &self,
73            f: impl FnOnce(&T) -> R,
74        ) -> Result<R, String> {
75            let endpoint = self
76                .inner
77                .lock()
78                .expect("schema value stream mutex poisoned");
79            let endpoint = endpoint
80                .as_ref()
81                .ok_or_else(|| "schema value stream was already transferred".to_string())?;
82            endpoint.downcast_ref::<T>().map(f).ok_or_else(|| {
83                "schema value stream endpoint belongs to an incompatible runtime".to_string()
84            })
85        }
86
87        #[doc(hidden)]
88        pub fn take_for_transfer(&self) -> Option<Self> {
89            self.inner
90                .lock()
91                .expect("schema value stream mutex poisoned")
92                .take()
93                .map(|endpoint| Self {
94                    inner: Arc::new(Mutex::new(Some(endpoint))),
95                })
96        }
97
98        #[doc(hidden)]
99        pub fn is_present(&self) -> bool {
100            self.inner
101                .lock()
102                .expect("schema value stream mutex poisoned")
103                .is_some()
104        }
105
106        #[doc(hidden)]
107        pub fn cell_id(&self) -> *const () {
108            Arc::as_ptr(&self.inner).cast()
109        }
110    }
111
112    impl std::fmt::Debug for SchemaValueStream {
113        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114            f.debug_tuple("SchemaValueStream")
115                .field(&if self.is_present() {
116                    "present"
117                } else {
118                    "consumed"
119                })
120                .finish()
121        }
122    }
123
124    impl PartialEq for SchemaValueStream {
125        fn eq(&self, other: &Self) -> bool {
126            Arc::ptr_eq(&self.inner, &other.inner)
127        }
128    }
129
130    /// Resource-table representation of `golem:core/types.schema-value-stream`.
131    /// It owns one affine runtime endpoint. Component Model readers are
132    /// created or consumed only by the embedding runtime while it has access
133    /// to the endpoint's Store.
134    #[cfg(feature = "host")]
135    pub struct SchemaValueStreamHandleRep {
136        stream: SchemaValueStream,
137    }
138
139    #[cfg(feature = "host")]
140    impl SchemaValueStreamHandleRep {
141        #[doc(hidden)]
142        pub fn new(stream: SchemaValueStream) -> Self {
143            Self { stream }
144        }
145
146        #[doc(hidden)]
147        pub fn into_stream(self) -> SchemaValueStream {
148            self.stream
149        }
150    }
151}
152
153#[cfg(all(feature = "guest", not(feature = "host")))]
154mod active {
155    use super::RawSchemaValueStream;
156    use crate::schema::wit::wire;
157    use std::future::Future;
158    use std::pin::Pin;
159    use std::sync::{Arc, Mutex, MutexGuard};
160
161    /// A lazy guest producer. Local reads retain errors; forwarding a failure
162    /// traps the producer because native streams have no error terminal.
163    pub trait SchemaValueStreamSource: Send {
164        fn next(
165            &mut self,
166        ) -> Pin<Box<dyn Future<Output = Result<Option<wire::SchemaValueTree>, String>> + Send + '_>>;
167    }
168
169    enum State {
170        Wrapped(wire::SchemaValueStream),
171        Native(RawSchemaValueStream),
172        Source(Box<dyn SchemaValueStreamSource>),
173    }
174
175    fn source_reader(mut source: Box<dyn SchemaValueStreamSource>) -> RawSchemaValueStream {
176        let (mut writer, reader) = crate::schema::wit::new_schema_value_stream();
177        wit_bindgen::spawn_local(async move {
178            loop {
179                match source.next().await {
180                    Ok(Some(item)) => {
181                        if writer.write_one(item).await.is_some() {
182                            break;
183                        }
184                    }
185                    Ok(None) => break,
186                    Err(error) => panic!("agent stream producer failed: {error}"),
187                }
188            }
189        });
190        reader
191    }
192
193    /// Guest-side stream leaf. A leaf can hold the recursive WIT resource or
194    /// the native reader obtained from it. Clones share one take-once state.
195    #[derive(Clone)]
196    pub struct SchemaValueStream {
197        inner: Arc<Mutex<Option<State>>>,
198    }
199
200    impl SchemaValueStream {
201        fn state(&self) -> MutexGuard<'_, Option<State>> {
202            self.inner
203                .lock()
204                .unwrap_or_else(|poison| poison.into_inner())
205        }
206
207        /// Creates an affine lazy source without starting a producer task.
208        pub fn from_source(source: impl SchemaValueStreamSource + 'static) -> Self {
209            Self {
210                inner: Arc::new(Mutex::new(Some(State::Source(Box::new(source))))),
211            }
212        }
213
214        #[doc(hidden)]
215        pub fn from_native(reader: RawSchemaValueStream) -> Self {
216            Self {
217                inner: Arc::new(Mutex::new(Some(State::Native(reader)))),
218            }
219        }
220
221        #[doc(hidden)]
222        pub fn from_wrapped(stream: wire::SchemaValueStream) -> Self {
223            Self {
224                inner: Arc::new(Mutex::new(Some(State::Wrapped(stream)))),
225            }
226        }
227
228        #[doc(hidden)]
229        pub fn is_present(&self) -> bool {
230            self.state().is_some()
231        }
232
233        #[doc(hidden)]
234        pub fn is_wrapped(&self) -> bool {
235            matches!(&*self.state(), Some(State::Wrapped(_)))
236        }
237
238        #[doc(hidden)]
239        pub fn cell_id(&self) -> *const () {
240            Arc::as_ptr(&self.inner).cast()
241        }
242
243        #[doc(hidden)]
244        pub fn take_wrapped(&self) -> Option<wire::SchemaValueStream> {
245            let mut state = self.state();
246            match state.take() {
247                Some(State::Wrapped(stream)) => Some(stream),
248                Some(native @ (State::Native(_) | State::Source(_))) => {
249                    *state = Some(native);
250                    None
251                }
252                None => None,
253            }
254        }
255
256        #[doc(hidden)]
257        pub async fn take_wrapped_async(&self) -> Result<wire::SchemaValueStream, String> {
258            let state = self
259                .state()
260                .take()
261                .ok_or_else(|| "schema value stream was already transferred".to_string())?;
262            Ok(match state {
263                State::Wrapped(stream) => stream,
264                State::Native(reader) => wire::SchemaValueStream::wrap(reader).await,
265                State::Source(source) => wire::SchemaValueStream::wrap(source_reader(source)).await,
266            })
267        }
268
269        #[doc(hidden)]
270        pub async fn ensure_wrapped(&self) -> Result<(), String> {
271            let state = self
272                .state()
273                .take()
274                .ok_or_else(|| "schema value stream was already transferred".to_string())?;
275            *self.state() = Some(match state {
276                State::Wrapped(stream) => State::Wrapped(stream),
277                State::Native(reader) => {
278                    State::Wrapped(wire::SchemaValueStream::wrap(reader).await)
279                }
280                State::Source(source) => {
281                    State::Wrapped(wire::SchemaValueStream::wrap(source_reader(source)).await)
282                }
283            });
284            Ok(())
285        }
286
287        #[doc(hidden)]
288        pub async fn take_native(self) -> Result<RawSchemaValueStream, String> {
289            let state = self
290                .state()
291                .take()
292                .ok_or_else(|| "schema value stream was already transferred".to_string())?;
293            Ok(match state {
294                State::Wrapped(stream) => wire::SchemaValueStream::unwrap(stream).await,
295                State::Native(reader) => reader,
296                State::Source(source) => source_reader(source),
297            })
298        }
299
300        #[doc(hidden)]
301        pub async fn next_wire(&self) -> Result<Option<wire::SchemaValueTree>, String> {
302            let state = self.state().take().ok_or_else(|| {
303                "schema value stream is already in use or was transferred".to_string()
304            })?;
305            if let State::Source(source) = state {
306                // Put the source back even when the caller cancels the read.
307                struct Restore<'a> {
308                    stream: &'a SchemaValueStream,
309                    source: Option<Box<dyn SchemaValueStreamSource>>,
310                }
311                impl Drop for Restore<'_> {
312                    fn drop(&mut self) {
313                        *self.stream.state() = self.source.take().map(State::Source);
314                    }
315                }
316                let mut restore = Restore {
317                    stream: self,
318                    source: Some(source),
319                };
320                return restore.source.as_mut().unwrap().next().await;
321            }
322            let mut reader = match state {
323                State::Wrapped(stream) => wire::SchemaValueStream::unwrap(stream).await,
324                State::Native(reader) => reader,
325                State::Source(_) => unreachable!(),
326            };
327            let item = reader.next().await;
328            *self.state() = Some(State::Native(reader));
329            Ok(item)
330        }
331    }
332
333    impl std::fmt::Debug for SchemaValueStream {
334        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335            let state = match &*self.state() {
336                Some(State::Wrapped(_)) => "wrapped",
337                Some(State::Native(_)) => "native",
338                Some(State::Source(_)) => "source",
339                None => "consumed",
340            };
341            f.debug_tuple("SchemaValueStream").field(&state).finish()
342        }
343    }
344
345    impl PartialEq for SchemaValueStream {
346        fn eq(&self, other: &Self) -> bool {
347            Arc::ptr_eq(&self.inner, &other.inner)
348        }
349    }
350}
351
352#[cfg(any(
353    all(
354        any(feature = "host", feature = "native-stream"),
355        not(feature = "guest")
356    ),
357    all(feature = "guest", not(feature = "host"))
358))]
359pub use active::SchemaValueStream;
360
361#[cfg(all(feature = "guest", not(feature = "host")))]
362pub use active::SchemaValueStreamSource;
363
364#[cfg(all(feature = "host", not(feature = "guest")))]
365pub use active::SchemaValueStreamHandleRep;
366
367#[cfg(not(any(
368    all(
369        any(feature = "host", feature = "native-stream"),
370        not(feature = "guest")
371    ),
372    all(feature = "guest", not(feature = "host"))
373)))]
374#[derive(Clone, Debug, PartialEq)]
375pub struct SchemaValueStream(());
376
377impl serde::Serialize for SchemaValueStream {
378    fn serialize<S: serde::Serializer>(&self, _serializer: S) -> Result<S::Ok, S::Error> {
379        Err(serde::ser::Error::custom(
380            "live schema value streams cannot be serialized",
381        ))
382    }
383}
384
385impl<'de> serde::Deserialize<'de> for SchemaValueStream {
386    fn deserialize<D: serde::Deserializer<'de>>(_deserializer: D) -> Result<Self, D::Error> {
387        Err(serde::de::Error::custom(
388            "live schema value streams cannot be deserialized",
389        ))
390    }
391}
392
393impl IntoSchema for SchemaValueStream {
394    fn type_id() -> TypeId {
395        TypeId::new("golem.core.SchemaValueStream")
396    }
397
398    fn register_in(_builder: &mut SchemaBuilder) -> SchemaType {
399        SchemaType::Stream {
400            inner: None,
401            metadata: MetadataEnvelope::default(),
402        }
403    }
404
405    fn to_value(&self) -> SchemaValue {
406        SchemaValue::Stream(self.clone())
407    }
408}
409
410impl FromSchema for SchemaValueStream {
411    fn from_value(value: &SchemaValue) -> Result<Self, FromSchemaError> {
412        match value {
413            SchemaValue::Stream(stream) => Ok(stream.clone()),
414            other => Err(FromSchemaError::shape_mismatch(
415                "stream",
416                format!("{other:?}"),
417                "SchemaValueStream",
418            )),
419        }
420    }
421}