1use std::future::Future;
22use std::pin::Pin;
23use std::sync::{Arc, Mutex};
24use std::task::{Context, Poll};
25
26use futures::StreamExt;
27use tracing::Instrument;
28
29use crate::error::ProviderError;
30use crate::observe::{AdapterContext, AdapterSlot};
31use crate::streaming::{Item, Streamed};
32use crate::wasm_compat::{WasmBoxedFuture, WasmBoxedStream, WasmCompatSend, WasmCompatSync};
33use crate::wire::document::Reassemble;
34use crate::wire::{
35 Call, Capabilities, Decoder, Descriptor, Flow, Mode, Operation, Out, Request, Response, Shared,
36 Wire, WireEvent,
37};
38
39mod dyn_model;
40pub(crate) mod http_transport;
41mod local;
42
43pub use dyn_model::DynModel;
44pub use local::{Local, Step};
45
46#[derive(Clone, Debug, Default, PartialEq)]
53pub struct Model<W, T = crate::http_client::DynHttpClient> {
54 pub wire: W,
56 pub transport: T,
58 prepared: crate::wire::Prepared,
60}
61
62impl<W, T> Model<W, T> {
63 pub fn new(wire: W, transport: T) -> Self {
65 Self {
66 wire,
67 transport,
68 prepared: crate::wire::Prepared::default(),
69 }
70 }
71}
72
73impl<W, T> Model<W, T>
74where
75 W: Wire<Op = crate::operation::Completion>,
76 T: Transport<W>,
77{
78 pub fn check(
101 &self,
102 request: &crate::completion::CompletionRequest,
103 ) -> Result<(), crate::completion::CheckError> {
104 check_with(request, |request| self.dry_run(request))
105 }
106}
107
108pub(crate) fn check_with(
114 request: &crate::completion::CompletionRequest,
115 run: impl FnOnce(crate::completion::CompletionRequest) -> Result<(), ProviderError>,
116) -> Result<(), crate::completion::CheckError> {
117 use crate::completion::{CheckError, OnUnsupported};
118 let mut request = without_messages(request);
119 if !request.options.is_default() {
120 request.options.on_unsupported = Some(OnUnsupported::Ignore);
121 }
122 match crate::completion::options::collect_refusals(|| run(request)) {
123 (Err(error), _) => Err(CheckError::Invalid(error)),
124 (Ok(()), refusals) if refusals.is_empty() => Ok(()),
125 (Ok(()), refusals) => Err(CheckError::Unsupported(refusals)),
126 }
127}
128
129pub(crate) fn without_messages(
132 request: &crate::completion::CompletionRequest,
133) -> crate::completion::CompletionRequest {
134 use crate::completion::{CompletionRequest, Message};
135 let CompletionRequest {
136 model,
137 chat_history,
138 documents,
139 tools,
140 temperature,
141 max_tokens,
142 tool_choice,
143 additional_params,
144 output_schema,
145 record_telemetry_content,
146 accept_unknown_finish_reasons,
147 foreign_reasoning,
148 options,
149 provider_options,
150 cache_points,
151 } = request;
152 let system = chat_history
153 .first()
154 .filter(|message| matches!(&***message, Message::System { .. }));
155 CompletionRequest {
156 model: model.clone(),
157 chat_history: system
158 .cloned()
159 .into_iter()
160 .chain([std::sync::Arc::new(Message::user("check"))])
161 .collect(),
162 documents: documents.clone(),
163 tools: tools.clone(),
164 temperature: *temperature,
165 max_tokens: *max_tokens,
166 tool_choice: tool_choice.clone(),
167 additional_params: additional_params.clone(),
168 output_schema: output_schema.clone(),
169 record_telemetry_content: *record_telemetry_content,
170 accept_unknown_finish_reasons: *accept_unknown_finish_reasons,
171 foreign_reasoning: *foreign_reasoning,
172 options: options.clone(),
173 provider_options: provider_options.clone(),
174 cache_points: cache_points.clone(),
175 }
176}
177
178pub trait Transport<W: Wire>: Clone + WasmCompatSend + WasmCompatSync + 'static {
185 fn send(&self, payload: W::Payload, exchange: Exchange) -> Opening<W::Frame>;
190}
191
192#[derive(Clone)]
194pub struct Exchange {
195 pub mode: Mode,
197 pub(crate) observation: Option<AdapterContext>,
199 pub(crate) extensions: http::Extensions,
202}
203
204#[derive(Default)]
207pub(crate) struct Sending {
208 pub(crate) observation: Option<AdapterContext>,
209 pub(crate) extensions: http::Extensions,
210}
211
212impl Sending {
213 pub(crate) fn observed(observation: AdapterContext) -> Self {
215 Self {
216 observation: Some(observation),
217 extensions: http::Extensions::new(),
218 }
219 }
220}
221
222pub struct Opening<F>(WasmBoxedFuture<'static, Result<Opened<F>, ProviderError>>);
224
225impl<F: WasmCompatSend + 'static> Opening<F> {
226 pub fn new(
228 open: impl Future<Output = Result<Opened<F>, ProviderError>> + WasmCompatSend + 'static,
229 ) -> Self {
230 Self(Box::pin(open))
231 }
232
233 pub fn ready(opened: Opened<F>) -> Self {
235 Self::new(std::future::ready(Ok(opened)))
236 }
237
238 pub fn failed(error: ProviderError) -> Self {
240 Self::new(std::future::ready(Err(error)))
241 }
242}
243
244impl<F> Future for Opening<F> {
245 type Output = Result<Opened<F>, ProviderError>;
246
247 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
248 self.0.as_mut().poll(cx)
249 }
250}
251
252pub struct Opened<F> {
255 pub(crate) frames: WasmBoxedStream<'static, Result<F, ProviderError>>,
256 pub(crate) request_id: Option<String>,
257 pub(crate) status: Option<http::StatusCode>,
258 pub(crate) headers: Option<http::HeaderMap>,
259 pub(crate) route: Option<String>,
260 pub(crate) document: Option<serde_json::Value>,
261 pub(crate) slot: Option<AdapterSlot>,
262 pub(crate) analysis_only: Option<fn(&F) -> bool>,
263}
264
265impl<F: WasmCompatSend + 'static> Opened<F> {
266 pub fn new(
268 frames: impl futures::Stream<Item = Result<F, ProviderError>> + WasmCompatSend + 'static,
269 ) -> Self {
270 Self {
271 frames: Box::pin(frames),
272 request_id: None,
273 status: None,
274 headers: None,
275 route: None,
276 document: None,
277 slot: None,
278 analysis_only: None,
279 }
280 }
281
282 pub fn failed(error: ProviderError) -> Self {
284 Self::new(futures::stream::once(async move { Err(error) }))
285 }
286
287 pub fn with_request_id(mut self, request_id: Option<String>) -> Self {
290 self.request_id = crate::provider_response::reported(request_id);
291 self
292 }
293
294 pub fn with_http(mut self, status: http::StatusCode, headers: http::HeaderMap) -> Self {
296 self.status = Some(status);
297 self.headers = Some(headers);
298 self
299 }
300
301 pub fn with_route(mut self, route: impl Into<String>) -> Self {
303 self.route = Some(route.into());
304 self
305 }
306
307 pub fn with_document(mut self, document: serde_json::Value) -> Self {
310 self.document = Some(document);
311 self
312 }
313
314 pub fn map_frames<S>(
317 mut self,
318 frames: impl FnOnce(WasmBoxedStream<'static, Result<F, ProviderError>>) -> S,
319 ) -> Self
320 where
321 S: futures::Stream<Item = Result<F, ProviderError>> + WasmCompatSend + 'static,
322 {
323 self.frames = Box::pin(frames(self.frames));
324 self
325 }
326}
327
328impl<W, T> Model<W, T>
329where
330 W: Wire,
331 T: Transport<W>,
332{
333 pub fn name(&self) -> &str {
335 self.wire.describe().name
336 }
337
338 pub fn id(&self) -> Option<&str> {
340 self.wire.describe().model
341 }
342
343 pub fn capabilities(&self) -> Capabilities {
346 self.wire.describe().capabilities
347 }
348
349 pub fn call(
354 &self,
355 request: impl Into<Request<W>>,
356 ) -> impl Future<Output = Result<Response<W>, ProviderError>> + WasmCompatSend + 'static {
357 self.finished(request.into(), Sending::default())
358 }
359
360 pub fn call_observed(
362 &self,
363 request: impl Into<Request<W>>,
364 observation: AdapterContext,
365 ) -> impl Future<Output = Result<Response<W>, ProviderError>> + WasmCompatSend + 'static {
366 self.finished(request.into(), Sending::observed(observation))
367 }
368
369 fn finished(
372 &self,
373 request: Request<W>,
374 sending: Sending,
375 ) -> impl Future<Output = Result<Response<W>, ProviderError>> + WasmCompatSend + 'static {
376 let model = self.clone();
377 async move { model.open(request, Mode::Unary, sending)?.finish().await }
378 }
379
380 pub fn stream(&self, request: impl Into<Request<W>>) -> Result<Streamed<W::Op>, ProviderError> {
384 self.open(request.into(), Mode::Streaming, Sending::default())
385 }
386
387 pub fn stream_observed(
389 &self,
390 request: impl Into<Request<W>>,
391 observation: AdapterContext,
392 ) -> Result<Streamed<W::Op>, ProviderError> {
393 self.open(
394 request.into(),
395 Mode::Streaming,
396 Sending::observed(observation),
397 )
398 }
399
400 pub(crate) async fn call_routed(
403 &self,
404 request: Request<W>,
405 ) -> Result<Response<W>, (ProviderError, String)> {
406 self.open(request, Mode::Unary, Sending::default())
407 .map_err(|error| (error, String::new()))?
408 .finish_routed()
409 .await
410 }
411
412 pub(crate) fn dry_run(&self, request: Request<W>) -> Result<(), ProviderError> {
415 let describe = Descriptor {
416 prepared: Some(&self.prepared),
417 ..self.wire.describe()
418 };
419 let request = <W::Op as Operation>::prepare(request, &describe)?;
420 self.wire.encode(request, Mode::Unary)?;
421 Ok(())
422 }
423
424 pub(crate) fn open(
428 &self,
429 request: Request<W>,
430 mode: Mode,
431 sending: Sending,
432 ) -> Result<Streamed<W::Op>, ProviderError> {
433 let describe = Descriptor {
434 prepared: Some(&self.prepared),
435 ..self.wire.describe()
436 };
437 let request = <W::Op as Operation>::prepare(request, &describe)?;
438 let provider = describe.name.to_owned();
439 let mut call = Call::new(&describe, mode);
440 let fold = <W::Op as Operation>::fold(&request, &mut call);
441 let span = call.span;
442 let payload = self.wire.encode(request, mode)?;
443 let Sending {
444 observation,
445 extensions,
446 } = sending;
447 let exchange = Exchange {
448 mode,
449 observation,
450 extensions,
451 };
452 let opening = self.transport.send(payload, exchange);
453 let shared = Arc::new(Mutex::new(Shared::new(fold)));
454 let reading = read(
455 self.wire.clone(),
456 opening,
457 Arc::clone(&shared),
458 span.clone(),
459 mode,
460 );
461 Ok(Streamed::new(reading, shared, span, provider))
462 }
463}
464
465fn read<W: Wire>(
476 wire: W,
477 opening: Opening<W::Frame>,
478 shared: Arc<Mutex<Shared<W::Op>>>,
479 span: tracing::Span,
480 mode: Mode,
481) -> WasmBoxedStream<'static, ()> {
482 let decoding = span.clone();
483 let reading = async_stream::stream! {
484 let reply: &Mutex<Shared<W::Op>> = &shared;
485 let opened = match mode {
487 Mode::Unary => opening.instrument(span.clone()).await,
488 Mode::Streaming => opening.await,
489 };
490 let Opened {
491 mut frames,
492 request_id,
493 status,
494 headers,
495 route,
496 document,
497 slot,
498 analysis_only,
499 } = match opened {
500 Ok(opened) => opened,
501 Err(error) => {
502 fail(reply, None, error);
503 return;
504 }
505 };
506 record_request_id(&span, request_id.as_deref());
507 let mut reassembler = document.is_none().then(|| wire.reassembler());
508 {
509 let mut state = lock(reply);
510 state.request_id.clone_from(&request_id);
511 state.document = document;
512 state.route = route.unwrap_or_default();
513 }
514 let enrich = |error: ProviderError| match mode {
515 Mode::Unary => error
517 .with_provider_status(status)
518 .with_provider_request_id(request_id.clone())
519 .with_response_headers(headers.clone()),
520 Mode::Streaming => error,
521 };
522 let mut decoder = wire.decoder();
523 let mut tally = slot.as_ref().map(|slot| Tally {
524 slot,
525 analysis_only,
526 counted: 0,
527 });
528 loop {
529 let flow = match frames.next().await {
530 Some(Ok(frame)) => step(
531 &mut decoder,
532 reassembler.as_mut(),
533 reply,
534 frame,
535 tally.as_mut(),
536 ),
537 Some(Err(error)) => {
538 record(reply, reassembler.map(|document| document.finish()));
539 fail(reply, slot.as_ref(), error);
540 return;
541 }
542 None => eof(&mut decoder, reply, tally.as_ref()),
543 };
544 match flow {
545 Ok(Flow::More) => yield (),
546 Ok(Flow::Ended(_)) => break,
547 Err(error) => {
548 record(reply, reassembler.map(|document| document.finish()));
549 fail(reply, slot.as_ref(), enrich(error));
550 return;
551 }
552 }
553 }
554 record(reply, reassembler.map(|document| document.finish()));
555 if let Some(slot) = &slot {
556 let usage = lock(reply)
557 .end
558 .as_ref()
559 .and_then(W::Op::observed_usage)
560 .cloned();
561 slot.terminal(usage.as_ref());
562 }
563 {
564 let state = lock(reply);
565 if let Some(document) = state.raw.as_ref().or(state.document.as_ref()) {
566 crate::providers::internal::trace_json(
567 crate::providers::internal::LogTarget::Completions,
568 "reply",
569 document,
570 );
571 }
572 }
573 yield ();
574 };
575 let mut reading: WasmBoxedStream<'static, ()> = Box::pin(reading);
576 match mode {
577 Mode::Streaming => Box::pin(futures::stream::poll_fn(move |cx| {
579 let _decoding = decoding.enter();
580 reading.as_mut().poll_next(cx)
581 })),
582 Mode::Unary => reading,
583 }
584}
585
586pub(crate) struct Decoded<Op: Operation> {
589 #[cfg(any(test, feature = "test-utils"))]
591 pub(crate) items: Vec<Result<crate::streaming::Item<Op::Event>, ProviderError>>,
592 pub(crate) outcome: Result<Op::Response, ProviderError>,
593}
594
595pub(crate) struct Tally<'a, F> {
598 slot: &'a AdapterSlot,
599 analysis_only: Option<fn(&F) -> bool>,
601 counted: usize,
602}
603
604pub(crate) fn triage<E>(event: WireEvent<E>) -> Result<Item<E>, ProviderError> {
609 match event {
610 WireEvent::Known(event) => Ok(Item::Event(event)),
611 WireEvent::Unknown { event_type, value } => {
612 warn_unmodeled(&event_type, &value);
613 Ok(Item::Unknown(value))
614 }
615 WireEvent::Corrupt(error) => Err(ProviderError::from(error)),
616 }
617}
618
619pub(crate) fn step<'id, Op, F, D, R>(
623 decoder: &mut D,
624 reassembler: Option<&mut R>,
625 reply: &'id Mutex<Shared<Op>>,
626 frame: F,
627 tally: Option<&mut Tally<'_, F>>,
628) -> Result<Flow, ProviderError>
629where
630 Op: Operation,
631 D: Decoder<'id, Op, F>,
632 R: Reassemble<D::Event>,
633{
634 let exempt = tally
635 .as_ref()
636 .and_then(|tally| tally.analysis_only)
637 .is_some_and(|analysis_only| analysis_only(&frame));
638 let classified = decoder.classify(frame);
639 if let Some(tally) = tally {
640 let corrupt = matches!(classified, WireEvent::Corrupt(_));
642 if corrupt || !exempt {
643 tally.counted += 1;
644 }
645 if corrupt {
646 tally.slot.corrupt(tally.counted);
647 }
648 }
649 match triage(classified)? {
650 Item::Event(event) => {
651 if let Some(reassembler) = reassembler {
652 reassembler.absorb(&event);
653 }
654 decoder.decode(event, Out::new(reply))
655 }
656 Item::Unknown(value) => {
657 Out::new(reply).unknown(value);
658 Ok(Flow::More)
659 }
660 }
661}
662
663fn eof<'id, Op, F, D>(
666 decoder: &mut D,
667 reply: &'id Mutex<Shared<Op>>,
668 tally: Option<&Tally<'_, F>>,
669) -> Result<Flow, ProviderError>
670where
671 Op: Operation,
672 D: Decoder<'id, Op, F>,
673{
674 if let Some(tally) = tally {
675 tally.slot.transport_eof(tally.counted);
676 }
677 let step = decoder.eof(Out::new(reply));
678 if !matches!(step, Ok(Flow::Ended(_)))
679 && let Some(tally) = tally
680 {
681 tally.slot.eof(tally.counted);
682 }
683 match step {
684 Ok(Flow::More) => Err(ProviderError::Truncated),
685 step => step,
686 }
687}
688
689pub(crate) fn feed<'id, Op, F, D, R>(
693 decoder: &mut D,
694 mut reassembler: Option<R>,
695 reply: &'id Mutex<Shared<Op>>,
696 frames: impl IntoIterator<Item = F>,
697) -> Result<(), ProviderError>
698where
699 Op: Operation,
700 D: Decoder<'id, Op, F>,
701 R: Reassemble<D::Event>,
702{
703 let fed = feed_until_end(decoder, reassembler.as_mut(), reply, frames);
704 record(reply, reassembler.map(|document| document.finish()));
705 fed
706}
707
708fn feed_until_end<'id, Op, F, D, R>(
709 decoder: &mut D,
710 mut reassembler: Option<&mut R>,
711 reply: &'id Mutex<Shared<Op>>,
712 frames: impl IntoIterator<Item = F>,
713) -> Result<(), ProviderError>
714where
715 Op: Operation,
716 D: Decoder<'id, Op, F>,
717 R: Reassemble<D::Event>,
718{
719 for frame in frames {
720 if let Flow::Ended(_) = step(decoder, reassembler.as_deref_mut(), reply, frame, None)? {
721 return Ok(());
722 }
723 }
724 eof(decoder, reply, None).map(drop)
725}
726
727pub(crate) fn record<Op: Operation>(
730 reply: &Mutex<Shared<Op>>,
731 document: Option<serde_json::Value>,
732) {
733 if let Some(document) = document.filter(|document| !document.is_null()) {
734 lock(reply).raw = Some(document);
735 }
736}
737
738pub(crate) fn settle<Op: Operation>(
742 shared: Mutex<Shared<Op>>,
743 fed: Result<(), ProviderError>,
744 reply: crate::wire::Reply,
745) -> Decoded<Op> {
746 let mut shared = shared
747 .into_inner()
748 .unwrap_or_else(std::sync::PoisonError::into_inner);
749 if let Err(error) = fed {
750 shared.items.push_back(Err(error));
751 }
752 shared.document = Some(reply.raw).filter(|raw| !raw.is_null());
753 shared.request_id = reply.provider_request_id;
754 let mut items = Vec::new();
755 while let Some(item) = shared.take() {
756 let failed = item.is_err();
757 items.push(item);
758 if failed {
759 break;
760 }
761 }
762 let outcome = match items.last() {
763 Some(Err(error)) => Err(error.clone()),
764 _ => shared.conclude(&reply.provider),
765 };
766 #[cfg(any(test, feature = "test-utils"))]
767 if let Err(error) = &outcome
768 && !matches!(items.last(), Some(Err(_)))
769 {
770 items.push(Err(error.clone()));
771 }
772 Decoded {
773 #[cfg(any(test, feature = "test-utils"))]
774 items,
775 outcome,
776 }
777}
778
779pub(crate) fn decode_reply<W: Wire>(
783 wire: &W,
784 request: &Request<W>,
785 mode: Mode,
786 frames: impl IntoIterator<Item = W::Frame>,
787 raw: serde_json::Value,
788) -> Result<Response<W>, ProviderError> {
789 let describe = wire.describe();
790 let fold = <W::Op as Operation>::fold(request, &mut Call::new(&describe, mode));
791 let shared = Mutex::new(Shared::new(fold));
792 let fed = feed(
793 &mut wire.decoder(),
794 Some(wire.reassembler()),
795 &shared,
796 frames,
797 );
798 let reply = crate::wire::Reply {
799 provider: describe.name.to_owned(),
800 raw,
801 provider_request_id: None,
802 };
803 settle(shared, fed, reply).outcome
804}
805
806#[cfg(any(test, feature = "test-utils"))]
809pub(crate) fn relay_frames<W>(
810 wire: &W,
811 frames: impl IntoIterator<Item = W::Frame>,
812) -> crate::streaming::StreamEvents
813where
814 W: Wire<Op = crate::operation::Completion>,
815{
816 use crate::error::ErrorReport;
817 use crate::streaming::Relayed;
818
819 let provider = wire.describe().name.to_owned();
820 let shared = Mutex::new(Shared::new(crate::operation::ReplyFold::relayed(
821 provider.clone(),
822 )));
823 let fed = feed(
824 &mut wire.decoder(),
825 Some(wire.reassembler()),
826 &shared,
827 frames,
828 );
829 let decoded = settle(
830 shared,
831 fed,
832 crate::wire::Reply {
833 provider,
834 raw: serde_json::Value::Null,
835 provider_request_id: None,
836 },
837 );
838 let origin = decoded
839 .outcome
840 .as_ref()
841 .map(|response| response.origin.clone())
842 .ok();
843 let relayed: Vec<Result<Relayed, ErrorReport>> = origin
844 .map(|origin| Ok(Relayed::Origin(origin)))
845 .into_iter()
846 .chain(decoded.items.into_iter().map(|item| match item {
847 Ok(item) => Ok(Relayed::Item(item)),
848 Err(error) => Err(ErrorReport::from(&error)),
849 }))
850 .chain(
852 decoded
853 .outcome
854 .ok()
855 .map(|response| Ok(Relayed::Done(Arc::new(response)))),
856 )
857 .collect();
858 Box::pin(futures::stream::iter(relayed))
859}
860
861#[cfg(test)]
862impl Decoded<crate::operation::Completion> {
863 pub(crate) fn events(&self) -> Vec<&crate::streaming::StreamEvent> {
865 self.items
866 .iter()
867 .filter_map(|item| match item {
868 Ok(crate::streaming::Item::Event(event)) => Some(event),
869 _ => None,
870 })
871 .collect()
872 }
873
874 pub(crate) fn ended(&self) -> Vec<crate::message::AssistantContent> {
876 self.events()
877 .into_iter()
878 .filter_map(|event| match event {
879 crate::streaming::StreamEvent::End { content, .. } => Some(content.clone()),
880 _ => None,
881 })
882 .collect()
883 }
884}
885
886#[cfg(test)]
889macro_rules! decode_events {
890 ($decoder:expr, $provider:expr, $events:expr) => {
891 $crate::driver::decode_with(
892 $crate::operation::ReplyFold::relayed($provider),
893 $provider,
894 |reply| {
895 let mut decoder = $decoder;
896 for event in $events {
897 if let $crate::wire::Flow::Ended(_) =
898 $crate::wire::Decoder::decode(&mut decoder, event, reply.out())?
899 {
900 return Ok(());
901 }
902 }
903 match $crate::wire::Decoder::eof(&mut decoder, reply.out())? {
904 $crate::wire::Flow::Ended(_) => Ok(()),
905 $crate::wire::Flow::More => Err($crate::error::ProviderError::Truncated),
906 }
907 },
908 )
909 };
910}
911#[cfg(test)]
912pub(crate) use decode_events;
913
914#[cfg(test)]
919macro_rules! feed_frames {
920 ($decoder:expr, $provider:expr, $frames:expr) => {
921 $crate::driver::decode_with(
922 $crate::operation::ReplyFold::relayed($provider),
923 $provider,
924 |reply| {
925 let mut decoder = $decoder;
926 reply.feed(
927 &mut decoder,
928 None::<$crate::wire::document::Unreassembled>,
929 $frames,
930 )
931 },
932 )
933 };
934 ($decoder:expr, $reassembler:expr, $provider:expr, $frames:expr) => {
935 $crate::driver::decode_with(
936 $crate::operation::ReplyFold::relayed($provider),
937 $provider,
938 |reply| {
939 let mut decoder = $decoder;
940 reply.feed(&mut decoder, Some($reassembler), $frames)
941 },
942 )
943 };
944}
945#[cfg(test)]
946pub(crate) use feed_frames;
947
948#[cfg(test)]
950pub(crate) struct Replying<'id, Op: Operation>(&'id Mutex<Shared<Op>>);
951
952#[cfg(test)]
953impl<'id, Op: Operation> Replying<'id, Op> {
954 pub(crate) fn out(&self) -> Out<'id, Op> {
956 Out::new(self.0)
957 }
958
959 pub(crate) fn feed<F, D: Decoder<'id, Op, F>, R: Reassemble<D::Event>>(
961 &self,
962 decoder: &mut D,
963 reassembler: Option<R>,
964 frames: impl IntoIterator<Item = F>,
965 ) -> Result<(), ProviderError> {
966 feed(decoder, reassembler, self.0, frames)
967 }
968}
969
970#[cfg(test)]
974pub(crate) fn decode_with<Op: Operation>(
975 fold: Op::Fold,
976 provider: &str,
977 run: impl for<'id> FnOnce(Replying<'id, Op>) -> Result<(), ProviderError>,
978) -> Decoded<Op> {
979 let shared = Mutex::new(Shared::new(fold));
980 let fed = run(Replying(&shared));
981 settle(
982 shared,
983 fed,
984 crate::wire::Reply {
985 provider: provider.to_owned(),
986 raw: serde_json::Value::Null,
987 provider_request_id: None,
988 },
989 )
990}
991
992fn fail<Op: Operation>(
994 reply: &Mutex<Shared<Op>>,
995 slot: Option<&AdapterSlot>,
996 error: ProviderError,
997) {
998 if let Some(slot) = slot {
999 slot.fail(&error);
1000 }
1001 lock(reply).items.push_back(Err(error));
1002}
1003
1004pub(crate) fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
1005 mutex
1006 .lock()
1007 .unwrap_or_else(std::sync::PoisonError::into_inner)
1008}
1009
1010pub(crate) async fn follow_cursors<P, Fut>(
1014 provider: &str,
1015 operation: &str,
1016 mut page: impl FnMut(Option<String>) -> Fut,
1017) -> Result<Vec<P>, ProviderError>
1018where
1019 Fut: Future<Output = Result<(P, Option<String>), ProviderError>>,
1020{
1021 let mut pages = Vec::new();
1022 let mut cursor = None;
1023 loop {
1024 let (read, next) = page(cursor.clone()).await?;
1025 pages.push(read);
1026 let Some(next) = next else { break };
1029 if cursor.as_deref() == Some(next.as_str()) {
1030 tracing::warn!(
1033 provider,
1034 operation,
1035 pages = pages.len(),
1036 "listing repeated its pagination cursor; returning the pages fetched so far"
1037 );
1038 break;
1039 }
1040 if pages.len() >= MAX_CONTINUATION_PAGES {
1041 tracing::warn!(
1042 provider,
1043 operation,
1044 pages = pages.len(),
1045 "listing hit its page ceiling with a cursor still advancing; returning the pages \
1046 fetched so far"
1047 );
1048 break;
1049 }
1050 cursor = Some(next);
1051 }
1052 Ok(pages)
1053}
1054
1055pub fn warn_unmodeled(kind: &str, payload: &impl serde::Serialize) {
1058 tracing::warn!(
1059 kind,
1060 payload_bytes = unknown_payload_bytes(payload),
1061 "skipping unmodeled wire payload"
1062 );
1063}
1064
1065fn unknown_payload_bytes(value: &impl serde::Serialize) -> u64 {
1068 struct CountingWriter(u64);
1071
1072 impl std::io::Write for CountingWriter {
1073 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1074 self.0 += buf.len() as u64;
1075 Ok(buf.len())
1076 }
1077
1078 fn flush(&mut self) -> std::io::Result<()> {
1079 Ok(())
1080 }
1081 }
1082
1083 let mut counter = CountingWriter(0);
1084 let _ = serde_json::to_writer(&mut counter, value);
1086 counter.0
1087}
1088
1089const MAX_CONTINUATION_PAGES: usize = 1000;
1092
1093pub(crate) fn record_request_id(span: &tracing::Span, request_id: Option<&str>) {
1095 if let Some(request_id) = request_id
1096 && !span.is_disabled()
1097 {
1098 span.record(crate::telemetry::PROVIDER_REQUEST_ID_FIELD, request_id);
1099 }
1100}
1101
1102#[cfg(test)]
1103pub(crate) mod tests;