Skip to main content

geam_core/provider/
call.rs

1use super::{
2    Callback, ProviderCallbackCodec, ProviderCallbackContext, ProviderExternalCodec,
3    ProviderExternalItem, ProviderStoredInput, ProviderStoredOutput, ProviderStoredOwner,
4    ProviderValueContext, Stored, Value,
5};
6use crate::provider::advanced::{ProviderDynamicInput, ProviderDynamicValue, StoredDynamic};
7use crate::{
8    HostCall, HostCallError, HostListType, HostProfile, HostProvider, HostStoredType, HostType,
9};
10use ecow::EcoString;
11use std::marker::PhantomData;
12
13/// Access to provider-owned state and active-call capabilities.
14///
15/// Provider functions receive this type through `#[geam::call]`. The macro
16/// replaces the placeholder context with one statically tied to the registered
17/// provider function.
18pub struct Call<State, Context = ProviderCallPlaceholder> {
19    context: Context,
20    state: PhantomData<fn() -> State>,
21}
22
23/// A provider failure that stops the active source execution.
24pub type HostResult<Value> = Result<Value, HostCallError>;
25
26#[doc(hidden)]
27pub struct ProviderCallPlaceholder;
28
29#[doc(hidden)]
30pub struct ProviderSharedCall<'state, State> {
31    state: &'state State,
32}
33
34#[doc(hidden)]
35pub struct ProviderActiveCall<'call, Profile, Provider, Return>
36where
37    Profile: HostProfile,
38    Provider: HostProvider<Profile>,
39    Return: HostType,
40{
41    call: HostCall<'call, Profile, Provider, Return>,
42}
43
44impl<'state, State> Call<State, ProviderSharedCall<'state, State>> {
45    pub fn state(&self) -> &State {
46        self.context.state
47    }
48
49    #[doc(hidden)]
50    pub fn from_shared_state(state: &'state State) -> Self {
51        Self {
52            context: ProviderSharedCall { state },
53            state: PhantomData,
54        }
55    }
56}
57
58impl<'call, Profile, Provider, Return>
59    Call<Provider::State, ProviderActiveCall<'call, Profile, Provider, Return>>
60where
61    Profile: HostProfile,
62    Provider: HostProvider<Profile>,
63    Return: HostType,
64{
65    pub fn state(&mut self) -> &Provider::State {
66        &*self.context.call.state()
67    }
68
69    pub fn state_mut(&mut self) -> &mut Provider::State {
70        self.context.call.state()
71    }
72
73    /// Compares two generic values with Gleam source equality semantics.
74    pub fn equal<Type, Host>(
75        &self,
76        left: &Value<Type, ProviderValueContext<'call, Host>>,
77        right: &Value<Type, ProviderValueContext<'call, Host>>,
78    ) -> bool
79    where
80        Host: HostType,
81        Host::Value<'call>: Clone,
82    {
83        self.context.call.equal::<Host>(left.host(), right.host())
84    }
85
86    /// Hashes a generic call-scoped value consistently with source equality.
87    ///
88    /// The result is an execution-local lookup key, not a stable serialized
89    /// value.
90    pub fn source_hash<Type, Host>(
91        &self,
92        value: &Value<Type, ProviderValueContext<'call, Host>>,
93    ) -> u64
94    where
95        Host: HostType,
96        Host::Value<'call>: Clone,
97    {
98        self.context.call.source_hash::<Host>(value.host())
99    }
100
101    /// Returns the canonical source-facing inspection of a generic value.
102    pub fn inspect<Type, Host>(
103        &self,
104        value: &Value<Type, ProviderValueContext<'call, Host>>,
105    ) -> EcoString
106    where
107        Host: HostType,
108        Host::Value<'call>: Clone,
109    {
110        self.context.call.inspect::<Host>(value.host())
111    }
112
113    /// Returns the length of an opaque generic List without decoding an item.
114    pub fn list_len<ListType, ItemHost>(
115        &self,
116        value: &Value<ListType, ProviderValueContext<'call, HostListType<ItemHost>>>,
117    ) -> usize
118    where
119        ItemHost: HostType,
120    {
121        self.context.call.list_len(value.host())
122    }
123
124    /// Reads one opaque generic List item as a call-scoped generic value.
125    pub fn list_get<ListType, Item, ItemHost>(
126        &mut self,
127        value: &Value<ListType, ProviderValueContext<'call, HostListType<ItemHost>>>,
128        index: usize,
129    ) -> Option<Value<Item, ProviderValueContext<'call, ItemHost>>>
130    where
131        ItemHost: HostType,
132    {
133        self.context
134            .call
135            .list_item(value.host(), index)
136            .map(Value::from_host)
137    }
138
139    /// Retains one generic source value for the generated external payload
140    /// that owns the returned field.
141    pub fn store<Type, Host, Owner, Index>(
142        &mut self,
143        value: Value<Type, ProviderValueContext<'call, Host>>,
144    ) -> Stored<Type, ProviderStoredOutput<'call, Owner, Index, Host>>
145    where
146        Host: HostType,
147        Owner: ProviderStoredOwner,
148    {
149        Stored::from_output(
150            self.context
151                .call
152                .provider_store::<HostStoredType<Index>, Host>(value.into_host()),
153        )
154    }
155
156    /// Restores one generic value selected from the active external input.
157    pub fn restore<Type, Host, Owner, Index>(
158        &mut self,
159        value: Stored<Type, ProviderStoredInput<'_, Owner, Index, Host>>,
160    ) -> Value<Type, ProviderValueContext<'call, Host>>
161    where
162        Host: HostType,
163        Owner: ProviderStoredOwner,
164    {
165        Value::from_host(
166            self.context
167                .call
168                .provider_restore::<Host, HostStoredType<Index>>(value.host()),
169        )
170    }
171
172    /// Reads the payload of a statically known external source value.
173    ///
174    /// This advanced bridge preserves the original external lease. It is used
175    /// when a retained generic field has already fixed its source type to one
176    /// generated external declaration.
177    #[doc(hidden)]
178    pub fn external_payload<Type>(
179        &self,
180        value: Value<Type, ProviderValueContext<'call, Type::Host>>,
181    ) -> ProviderExternalItem<Type>
182    where
183        Type: ProviderExternalCodec<Profile>,
184    {
185        Type::input(&self.context.call, value.into_host())
186    }
187
188    /// Retains one call-scoped generic value with its exact specialized type.
189    pub fn store_dynamic<Value, Owner>(&mut self, value: Value) -> StoredDynamic<Owner>
190    where
191        Value: ProviderDynamicValue<'call, Profile, Provider, Return>,
192        Owner: ProviderStoredOwner,
193    {
194        let value = value.into_host(&mut self.context.call);
195        StoredDynamic::new(
196            self.context
197                .call
198                .provider_store_dynamic::<Value::Host>(value),
199        )
200    }
201
202    /// Restores an existential value only when its exact specialized type
203    /// matches the requested generated input codec.
204    pub fn restore_dynamic<Type, Owner>(
205        &mut self,
206        value: &StoredDynamic<Owner>,
207    ) -> Option<Type::View<'call>>
208    where
209        Type: ProviderDynamicInput<Profile, Provider, Return>,
210        Owner: ProviderStoredOwner,
211    {
212        let value = self
213            .context
214            .call
215            .provider_restore_dynamic::<Type::Host>(value.host())?;
216        Some(Type::from_host(&mut self.context.call, value))
217    }
218
219    /// Restores an existential value with the exact specialization of an
220    /// existing call-scoped generic value.
221    pub fn restore_dynamic_value<Type, Host, Owner>(
222        &mut self,
223        value: &StoredDynamic<Owner>,
224        _type_witness: &Value<Type, ProviderValueContext<'call, Host>>,
225    ) -> Option<Value<Type, ProviderValueContext<'call, Host>>>
226    where
227        Host: HostType,
228        Owner: ProviderStoredOwner,
229    {
230        self.context
231            .call
232            .provider_restore_dynamic::<Host>(value.host())
233            .map(Value::from_host)
234    }
235
236    /// Invokes one typed Gleam callback within this active provider call.
237    pub fn invoke<Signature, Codec>(
238        &mut self,
239        callback: Callback<
240            Signature,
241            ProviderCallbackContext<'call, Profile, Provider, Return, Codec>,
242        >,
243        arguments: Codec::Arguments,
244    ) -> HostResult<Codec::Returned>
245    where
246        Codec: ProviderCallbackCodec<'call, Profile, Provider, Return>,
247    {
248        callback.invoke(&mut self.context.call, arguments)
249    }
250
251    #[doc(hidden)]
252    pub fn from_host_call(call: HostCall<'call, Profile, Provider, Return>) -> Self {
253        Self {
254            context: ProviderActiveCall { call },
255            state: PhantomData,
256        }
257    }
258
259    #[doc(hidden)]
260    pub fn into_host_call(self) -> HostCall<'call, Profile, Provider, Return> {
261        self.context.call
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::{Call, HostResult};
268    use crate::host::CallArguments;
269    use crate::host::HostCallErrorKind;
270    use crate::host::test::{TestHostCallRuntime, TestHostProfile, TestRunState};
271    use crate::host::{
272        HostCallable, HostFunctionToken, HostScopedValue, HostTypeList, HostTypeListEnd,
273        HostTypeParameter, HostValue, HostValueFamily, HostValueToken,
274    };
275    use crate::provider::{
276        Callback, ProviderCallbackCodec, ProviderCallbackContext, ProviderConstructions,
277        ProviderNoConstructions, ProviderValueContext, Value,
278    };
279    use crate::{HostCall, HostFailure, HostProvider};
280    use num_bigint::BigInt;
281
282    struct Provider;
283
284    impl HostProvider<TestHostProfile> for Provider {
285        type State = TestRunState;
286
287        fn project(state: &mut TestRunState) -> &mut Self::State {
288            state
289        }
290    }
291
292    struct IntCallbackCodec;
293
294    impl<'call> ProviderCallbackCodec<'call, TestHostProfile, Provider, BigInt> for IntCallbackCodec {
295        type HostArguments = HostTypeList<BigInt, HostTypeListEnd>;
296        type HostReturn = BigInt;
297        type Arguments = (BigInt,);
298        type Returned = BigInt;
299        type Requirements = ProviderNoConstructions;
300
301        fn into_host_arguments(
302            arguments: Self::Arguments,
303            _call: &mut HostCall<'call, TestHostProfile, Provider, BigInt>,
304            _constructions: &ProviderConstructions<'call, Self::Requirements>,
305        ) -> <Self::HostArguments as crate::HostTypeSequence>::Values<'call> {
306            (arguments.0, ())
307        }
308
309        fn from_host_return(
310            value: <Self::HostReturn as crate::HostType>::Value<'call>,
311            _call: &mut HostCall<'call, TestHostProfile, Provider, BigInt>,
312        ) -> Self::Returned {
313            value
314        }
315    }
316
317    #[test]
318    fn shared_call_exposes_only_the_borrowed_provider_state() {
319        let state = TestRunState {
320            counter: 7,
321            unrelated: true,
322        };
323        let call = Call::from_shared_state(&state);
324
325        assert_eq!(call.state().counter, 7);
326        assert!(call.state().unrelated);
327    }
328
329    #[test]
330    fn active_call_projects_shared_and_mutable_provider_state() {
331        let mut state = TestRunState::default();
332        {
333            let mut runtime =
334                TestHostCallRuntime::new(&mut state, CallArguments::new(Vec::new(), Vec::new()));
335            let host_call = HostCall::<TestHostProfile, Provider, bool>::new(&mut runtime);
336            let mut call = Call::from_host_call(host_call);
337
338            assert_eq!(call.state().counter, 0);
339            call.state_mut().counter = 3;
340            assert_eq!(call.state().counter, 3);
341
342            let _recovered_call = call.into_host_call();
343        }
344        assert_eq!(state.counter, 3);
345    }
346
347    #[test]
348    fn host_result_preserves_the_local_failure_envelope() {
349        fn fail() -> HostResult<()> {
350            Err(HostFailure::new("provider unavailable").into())
351        }
352
353        assert_eq!(
354            fail()
355                .expect_err("host failure should stop the call")
356                .into_kind(),
357            HostCallErrorKind::Failure(HostFailure::new("provider unavailable")),
358        );
359    }
360
361    #[test]
362    fn active_call_owns_generic_source_semantics_without_materializing_values() {
363        type Parameter = HostTypeParameter<0>;
364        let mut state = TestRunState::default();
365        let mut runtime =
366            TestHostCallRuntime::new(&mut state, CallArguments::new(Vec::new(), Vec::new()));
367        let host = HostValue::<Parameter>::new(HostValueToken {
368            family: HostValueFamily::String,
369            index: 2,
370        });
371        let left = Value::<Parameter, ProviderValueContext<'_, Parameter>>::from_host(host);
372        let right = Value::<Parameter, ProviderValueContext<'_, Parameter>>::from_host(host);
373        let host_call = HostCall::<TestHostProfile, Provider, bool>::new(&mut runtime);
374        let call = Call::from_host_call(host_call);
375
376        assert!(!call.equal(&left, &right));
377        assert_eq!(call.source_hash(&left), 17);
378        assert_eq!(call.inspect(&left), "inspected");
379    }
380
381    #[test]
382    fn active_call_invokes_one_static_callback_codec() {
383        type Context<'call> =
384            ProviderCallbackContext<'call, TestHostProfile, Provider, BigInt, IntCallbackCodec>;
385        let mut state = TestRunState::default();
386        let mut runtime =
387            TestHostCallRuntime::new(&mut state, CallArguments::new(Vec::new(), Vec::new()));
388        let host_call = HostCall::<TestHostProfile, Provider, BigInt>::new(&mut runtime);
389        let mut call = Call::from_host_call(host_call);
390        let constructions = ProviderConstructions::none();
391        let constructions = Clone::clone(&constructions);
392        let callback = Callback::<fn(BigInt) -> BigInt, Context<'_>>::from_host(
393            HostCallable::new(HostFunctionToken(3)),
394            constructions,
395        );
396        let callback = Clone::clone(&callback);
397
398        let returned = call
399            .invoke(callback, (BigInt::from(7),))
400            .expect("typed callback should invoke through the active call");
401        assert_eq!(returned, BigInt::from(0));
402        assert_eq!(runtime.completed(), Some(&HostScopedValue::Int(7.into())));
403    }
404}