1#[cfg(any(
29 feature = "rustls-aws",
30 feature = "rustls-ring",
31 feature = "native-tls"
32))]
33use core::time::Duration;
34use core::{any::Any, fmt};
35
36#[cfg(any(
37 feature = "rustls-aws",
38 feature = "rustls-ring",
39 feature = "native-tls"
40))]
41use alloc::string::ToString;
42use alloc::{boxed::Box, collections::BTreeMap, string::String, vec, vec::Vec};
43
44use std::io::{self, Read, Write};
45
46#[cfg(any(
47 feature = "rustls-aws",
48 feature = "rustls-ring",
49 feature = "native-tls"
50))]
51use pimalaya_stream::{std::stream::StreamStd, tls::Tls};
52use secrecy::SecretString;
53use thiserror::Error;
54use url::Url;
55
56use crate::{
57 coroutine::*,
58 rfc8620::{
59 blob_download::*,
60 blob_upload::*,
61 changes::JmapChangesOutput,
62 coroutine::JmapRedirectYield,
63 push_subscription::{get::*, set::*},
64 request::{JmapRequest, JmapResponse},
65 send::*,
66 session::JmapSession,
67 session_get::*,
68 },
69 rfc8621::{
70 email::{changes::*, copy::*, get::*, import::*, parse::*, query::*, set::*},
71 email_submission::{cancel::*, get::*, query::*, set::*},
72 identity::{get::*, set::*},
73 mailbox::{changes::*, get::*, query::*, set::*},
74 thread::{changes::*, get::*},
75 vacation_response::{get::*, set::*, *},
76 },
77 rfc9610::{
78 address_book::{changes::*, get::*, set::*},
79 contact_card::{changes::*, copy::*, get::*, query::*, set::*},
80 },
81};
82
83#[derive(Debug, Error)]
85pub enum JmapClientStdError {
86 #[error(transparent)]
88 Send(#[from] JmapSendError),
89 #[error(transparent)]
91 SessionGet(#[from] JmapSessionGetError),
92 #[error(transparent)]
94 BlobUpload(#[from] JmapBlobUploadError),
95 #[error(transparent)]
97 BlobDownload(#[from] JmapBlobDownloadError),
98 #[error(transparent)]
100 PushSubscriptionGet(#[from] JmapPushSubscriptionGetError),
101 #[error(transparent)]
103 PushSubscriptionSet(#[from] JmapPushSubscriptionSetError),
104 #[error(transparent)]
106 MailboxGet(#[from] JmapMailboxGetError),
107 #[error(transparent)]
109 MailboxQuery(#[from] JmapMailboxQueryError),
110 #[error(transparent)]
112 MailboxSet(#[from] JmapMailboxSetError),
113 #[error(transparent)]
115 MailboxChanges(#[from] JmapMailboxChangesError),
116 #[error(transparent)]
118 EmailGet(#[from] JmapEmailGetError),
119 #[error(transparent)]
121 EmailQuery(#[from] JmapEmailQueryError),
122 #[error(transparent)]
124 EmailSet(#[from] JmapEmailSetError),
125 #[error(transparent)]
127 EmailChanges(#[from] JmapEmailChangesError),
128 #[error(transparent)]
130 EmailCopy(#[from] JmapEmailCopyError),
131 #[error(transparent)]
133 EmailImport(#[from] JmapEmailImportError),
134 #[error(transparent)]
136 EmailParse(#[from] JmapEmailParseError),
137 #[error(transparent)]
139 ThreadGet(#[from] JmapThreadGetError),
140 #[error(transparent)]
142 ThreadChanges(#[from] JmapThreadChangesError),
143 #[error(transparent)]
145 IdentityGet(#[from] JmapIdentityGetError),
146 #[error(transparent)]
148 IdentitySet(#[from] JmapIdentitySetError),
149 #[error(transparent)]
151 EmailSubmissionGet(#[from] JmapEmailSubmissionGetError),
152 #[error(transparent)]
154 EmailSubmissionQuery(#[from] JmapEmailSubmissionQueryError),
155 #[error(transparent)]
157 EmailSubmissionSet(#[from] JmapEmailSubmissionSetError),
158 #[error(transparent)]
160 EmailSubmissionCancel(#[from] JmapEmailSubmissionCancelError),
161 #[error(transparent)]
163 VacationResponseGet(#[from] JmapVacationResponseGetError),
164 #[error(transparent)]
166 VacationResponseSet(#[from] JmapVacationResponseSetError),
167 #[error(transparent)]
169 AddressBookGet(#[from] JmapAddressBookGetError),
170 #[error(transparent)]
172 AddressBookSet(#[from] JmapAddressBookSetError),
173 #[error(transparent)]
175 AddressBookChanges(#[from] JmapAddressBookChangesError),
176 #[error(transparent)]
178 ContactCardGet(#[from] JmapContactCardGetError),
179 #[error(transparent)]
181 ContactCardQuery(#[from] JmapContactCardQueryError),
182 #[error(transparent)]
184 ContactCardSet(#[from] JmapContactCardSetError),
185 #[error(transparent)]
187 ContactCardChanges(#[from] JmapContactCardChangesError),
188 #[error(transparent)]
190 ContactCardCopy(#[from] JmapContactCardCopyError),
191 #[error(transparent)]
193 Io(#[from] io::Error),
194 #[cfg(any(
196 feature = "rustls-aws",
197 feature = "rustls-ring",
198 feature = "native-tls"
199 ))]
200 #[error(transparent)]
201 Tls(#[from] anyhow::Error),
202 #[cfg(any(
204 feature = "rustls-aws",
205 feature = "rustls-ring",
206 feature = "native-tls"
207 ))]
208 #[error("JMAP URL `{0}` has no host")]
209 UrlMissingHost(String),
210 #[cfg(any(
212 feature = "rustls-aws",
213 feature = "rustls-ring",
214 feature = "native-tls"
215 ))]
216 #[error(
217 "JMAP URL `{url}` has unsupported scheme `{scheme}` (expected `http`, `https`, `jmap` or `jmaps`)"
218 )]
219 UrlUnsupportedScheme {
220 url: String,
222 scheme: String,
224 },
225 #[error("JMAP server redirected to `{0}` during a non-redirectable operation")]
228 UnexpectedRedirect(Url),
229 #[error("JMAP client missing session; call `session_get` first")]
231 MissingSession,
232}
233
234const READ_BUFFER_SIZE: usize = 16 * 1024;
235
236pub struct JmapClientStd {
238 pub stream: Box<dyn JmapStream>,
240 pub http_auth: SecretString,
242 pub session: Option<JmapSession>,
244}
245
246impl JmapClientStd {
247 pub fn new<S: Read + Write + Send + 'static>(stream: S, http_auth: SecretString) -> Self {
251 Self {
252 stream: Box::new(stream),
253 http_auth,
254 session: None,
255 }
256 }
257
258 pub fn default_alpn() -> Vec<String> {
262 vec![String::from("http/1.1")]
263 }
264
265 pub fn run<C, T, E>(&mut self, mut coroutine: C) -> Result<T, JmapClientStdError>
273 where
274 C: JmapCoroutine<Yield = JmapYield, Return = Result<T, E>>,
275 JmapClientStdError: From<E>,
276 {
277 let mut buf = [0u8; READ_BUFFER_SIZE];
278 let mut arg: Option<&[u8]> = None;
279
280 loop {
281 match coroutine.resume(arg.take()) {
282 JmapCoroutineState::Complete(Ok(out)) => return Ok(out),
283 JmapCoroutineState::Complete(Err(err)) => return Err(err.into()),
284 JmapCoroutineState::Yielded(JmapYield::WantsRead) => {
285 let n = self.stream.read(&mut buf)?;
286 arg = Some(&buf[..n]);
287 }
288 JmapCoroutineState::Yielded(JmapYield::WantsWrite(bytes)) => {
289 self.stream.write_all(&bytes)?;
290 arg = None;
291 }
292 }
293 }
294 }
295
296 pub fn from_parts<S: Read + Write + Send + 'static>(
299 stream: S,
300 http_auth: SecretString,
301 session: JmapSession,
302 ) -> Self {
303 Self {
304 stream: Box::new(stream),
305 http_auth,
306 session: Some(session),
307 }
308 }
309
310 #[cfg(any(
314 feature = "rustls-aws",
315 feature = "rustls-ring",
316 feature = "native-tls"
317 ))]
318 pub fn connect(
319 url: &Url,
320 tls: &Tls,
321 http_auth: SecretString,
322 ) -> Result<Self, JmapClientStdError> {
323 let host = url
324 .host_str()
325 .ok_or_else(|| JmapClientStdError::UrlMissingHost(url.to_string()))?;
326
327 let stream = match url.scheme() {
328 "http" | "jmap" => StreamStd::connect_tcp(host, url.port().unwrap_or(80))?,
329 "https" | "jmaps" => StreamStd::connect_tls(host, url.port().unwrap_or(443), tls)?,
330 scheme => {
331 return Err(JmapClientStdError::UrlUnsupportedScheme {
332 url: url.to_string(),
333 scheme: scheme.to_string(),
334 });
335 }
336 };
337
338 stream.set_read_timeout(Some(Duration::from_secs(5)))?;
342
343 Ok(Self {
344 stream: Box::new(stream),
345 http_auth,
346 session: None,
347 })
348 }
349
350 pub fn set_stream<S: Read + Write + Send + 'static>(&mut self, stream: S) {
354 self.stream = Box::new(stream);
355 }
356
357 pub fn session(&self) -> Option<&JmapSession> {
359 self.session.as_ref()
360 }
361
362 pub fn http_auth(&self) -> &SecretString {
364 &self.http_auth
365 }
366
367 fn session_or_err(&self) -> Result<&JmapSession, JmapClientStdError> {
368 self.session
369 .as_ref()
370 .ok_or(JmapClientStdError::MissingSession)
371 }
372
373 pub fn session_get(&mut self, url: &Url) -> Result<&JmapSession, JmapClientStdError> {
379 let mut coroutine = JmapSessionGet::new(&self.http_auth, url);
380 let mut buf = [0u8; READ_BUFFER_SIZE];
381 let mut arg: Option<&[u8]> = None;
382
383 loop {
384 match coroutine.resume(arg.take()) {
385 JmapCoroutineState::Complete(Ok(JmapSessionGetOutput { session, .. })) => {
386 self.session = Some(session);
387 return Ok(self.session.as_ref().unwrap());
388 }
389 JmapCoroutineState::Complete(Err(err)) => return Err(err.into()),
390 JmapCoroutineState::Yielded(JmapRedirectYield::WantsRead) => {
391 let n = self.stream.read(&mut buf)?;
392 arg = Some(&buf[..n]);
393 }
394 JmapCoroutineState::Yielded(JmapRedirectYield::WantsWrite(bytes)) => {
395 self.stream.write_all(&bytes)?;
396 arg = None;
397 }
398 JmapCoroutineState::Yielded(JmapRedirectYield::WantsRedirect { url, .. }) => {
399 return Err(JmapClientStdError::UnexpectedRedirect(url));
400 }
401 }
402 }
403 }
404
405 pub fn send_raw(&mut self, request: JmapRequest) -> Result<JmapResponse, JmapClientStdError> {
409 let session = self.session_or_err()?;
410 let coroutine = JmapSend::new(&self.http_auth, &session.api_url, request)?;
411 let out = self.run(coroutine)?;
412 Ok(out.response)
413 }
414
415 pub fn blob_upload(
419 &mut self,
420 upload_url: &Url,
421 content_type: &str,
422 data: Vec<u8>,
423 ) -> Result<JmapBlobUploadOutput, JmapClientStdError> {
424 let mut coroutine = JmapBlobUpload::new(&self.http_auth, upload_url, content_type, data);
425 let mut buf = [0u8; READ_BUFFER_SIZE];
426 let mut arg: Option<&[u8]> = None;
427
428 loop {
429 match coroutine.resume(arg.take()) {
430 JmapCoroutineState::Complete(Ok(out)) => return Ok(out),
431 JmapCoroutineState::Complete(Err(err)) => return Err(err.into()),
432 JmapCoroutineState::Yielded(JmapRedirectYield::WantsRead) => {
433 let n = self.stream.read(&mut buf)?;
434 arg = Some(&buf[..n]);
435 }
436 JmapCoroutineState::Yielded(JmapRedirectYield::WantsWrite(bytes)) => {
437 self.stream.write_all(&bytes)?;
438 arg = None;
439 }
440 JmapCoroutineState::Yielded(JmapRedirectYield::WantsRedirect { url, .. }) => {
441 return Err(JmapClientStdError::UnexpectedRedirect(url));
442 }
443 }
444 }
445 }
446
447 pub fn blob_download(&mut self, download_url: &Url) -> Result<Vec<u8>, JmapClientStdError> {
451 let mut coroutine = JmapBlobDownload::new(&self.http_auth, download_url);
452 let mut buf = [0u8; READ_BUFFER_SIZE];
453 let mut arg: Option<&[u8]> = None;
454
455 loop {
456 match coroutine.resume(arg.take()) {
457 JmapCoroutineState::Complete(Ok(out)) => return Ok(out.data),
458 JmapCoroutineState::Complete(Err(err)) => return Err(err.into()),
459 JmapCoroutineState::Yielded(JmapRedirectYield::WantsRead) => {
460 let n = self.stream.read(&mut buf)?;
461 arg = Some(&buf[..n]);
462 }
463 JmapCoroutineState::Yielded(JmapRedirectYield::WantsWrite(bytes)) => {
464 self.stream.write_all(&bytes)?;
465 arg = None;
466 }
467 JmapCoroutineState::Yielded(JmapRedirectYield::WantsRedirect { url, .. }) => {
468 return Err(JmapClientStdError::UnexpectedRedirect(url));
469 }
470 }
471 }
472 }
473
474 pub fn push_subscription_get(
476 &mut self,
477 opts: JmapPushSubscriptionGetOptions,
478 ) -> Result<JmapPushSubscriptionGetOutput, JmapClientStdError> {
479 let coroutine =
480 JmapPushSubscriptionGet::new(self.session_or_err()?, &self.http_auth, opts)?;
481 self.run(coroutine)
482 }
483
484 pub fn push_subscription_set(
486 &mut self,
487 args: JmapPushSubscriptionSetArgs,
488 ) -> Result<JmapPushSubscriptionSetOutput, JmapClientStdError> {
489 let coroutine =
490 JmapPushSubscriptionSet::new(self.session_or_err()?, &self.http_auth, args)?;
491 self.run(coroutine)
492 }
493
494 pub fn mailbox_get(
496 &mut self,
497 opts: JmapMailboxGetOptions,
498 ) -> Result<JmapMailboxGetOutput, JmapClientStdError> {
499 let coroutine = JmapMailboxGet::new(self.session_or_err()?, &self.http_auth, opts)?;
500 self.run(coroutine)
501 }
502
503 pub fn mailbox_query(
506 &mut self,
507 opts: JmapMailboxQueryOptions,
508 ) -> Result<JmapMailboxQueryOutput, JmapClientStdError> {
509 let coroutine = JmapMailboxQuery::new(self.session_or_err()?, &self.http_auth, opts)?;
510 self.run(coroutine)
511 }
512
513 pub fn mailbox_set(
515 &mut self,
516 args: JmapMailboxSetArgs,
517 ) -> Result<JmapMailboxSetOutput, JmapClientStdError> {
518 let coroutine = JmapMailboxSet::new(self.session_or_err()?, &self.http_auth, args)?;
519 self.run(coroutine)
520 }
521
522 pub fn mailbox_changes(
524 &mut self,
525 since_state: impl Into<String>,
526 opts: JmapMailboxChangesOptions,
527 ) -> Result<JmapChangesOutput, JmapClientStdError> {
528 let coroutine =
529 JmapMailboxChanges::new(self.session_or_err()?, &self.http_auth, since_state, opts)?;
530 self.run(coroutine)
531 }
532
533 pub fn email_get(
535 &mut self,
536 ids: Vec<String>,
537 opts: JmapEmailGetOptions,
538 ) -> Result<JmapEmailGetOutput, JmapClientStdError> {
539 let coroutine = JmapEmailGet::new(self.session_or_err()?, &self.http_auth, ids, opts)?;
540 self.run(coroutine)
541 }
542
543 pub fn email_query(
545 &mut self,
546 opts: JmapEmailQueryOptions,
547 ) -> Result<JmapEmailQueryOutput, JmapClientStdError> {
548 let coroutine = JmapEmailQuery::new(self.session_or_err()?, &self.http_auth, opts)?;
549 self.run(coroutine)
550 }
551
552 pub fn email_set(
554 &mut self,
555 args: JmapEmailSetArgs,
556 ) -> Result<JmapEmailSetOutput, JmapClientStdError> {
557 let coroutine = JmapEmailSet::new(self.session_or_err()?, &self.http_auth, args)?;
558 self.run(coroutine)
559 }
560
561 pub fn email_changes(
563 &mut self,
564 since_state: impl Into<String>,
565 opts: JmapEmailChangesOptions,
566 ) -> Result<JmapChangesOutput, JmapClientStdError> {
567 let coroutine =
568 JmapEmailChanges::new(self.session_or_err()?, &self.http_auth, since_state, opts)?;
569 self.run(coroutine)
570 }
571
572 pub fn email_copy(
574 &mut self,
575 from_account_id: impl Into<String>,
576 emails: BTreeMap<String, JmapEmailCopyArgs>,
577 ) -> Result<JmapEmailCopyOutput, JmapClientStdError> {
578 let coroutine = JmapEmailCopy::new(
579 self.session_or_err()?,
580 &self.http_auth,
581 from_account_id,
582 emails,
583 )?;
584 self.run(coroutine)
585 }
586
587 pub fn email_import(
589 &mut self,
590 emails: BTreeMap<String, JmapEmailImportArgs>,
591 ) -> Result<JmapEmailImportOutput, JmapClientStdError> {
592 let coroutine = JmapEmailImport::new(self.session_or_err()?, &self.http_auth, emails)?;
593 self.run(coroutine)
594 }
595
596 pub fn email_parse(
598 &mut self,
599 blob_ids: Vec<String>,
600 opts: JmapEmailParseOptions,
601 ) -> Result<JmapEmailParseOutput, JmapClientStdError> {
602 let coroutine =
603 JmapEmailParse::new(self.session_or_err()?, &self.http_auth, blob_ids, opts)?;
604 self.run(coroutine)
605 }
606
607 pub fn thread_get(
609 &mut self,
610 ids: Vec<String>,
611 ) -> Result<JmapThreadGetOutput, JmapClientStdError> {
612 let coroutine = JmapThreadGet::new(self.session_or_err()?, &self.http_auth, ids)?;
613 self.run(coroutine)
614 }
615
616 pub fn thread_changes(
618 &mut self,
619 since_state: impl Into<String>,
620 opts: JmapThreadChangesOptions,
621 ) -> Result<JmapChangesOutput, JmapClientStdError> {
622 let coroutine =
623 JmapThreadChanges::new(self.session_or_err()?, &self.http_auth, since_state, opts)?;
624 self.run(coroutine)
625 }
626
627 pub fn identity_get(
629 &mut self,
630 opts: JmapIdentityGetOptions,
631 ) -> Result<JmapIdentityGetOutput, JmapClientStdError> {
632 let coroutine = JmapIdentityGet::new(self.session_or_err()?, &self.http_auth, opts)?;
633 self.run(coroutine)
634 }
635
636 pub fn identity_set(
638 &mut self,
639 args: JmapIdentitySetArgs,
640 ) -> Result<JmapIdentitySetOutput, JmapClientStdError> {
641 let coroutine = JmapIdentitySet::new(self.session_or_err()?, &self.http_auth, args)?;
642 self.run(coroutine)
643 }
644
645 pub fn email_submission_get(
647 &mut self,
648 opts: JmapEmailSubmissionGetOptions,
649 ) -> Result<JmapEmailSubmissionGetOutput, JmapClientStdError> {
650 let coroutine = JmapEmailSubmissionGet::new(self.session_or_err()?, &self.http_auth, opts)?;
651 self.run(coroutine)
652 }
653
654 pub fn email_submission_query(
657 &mut self,
658 opts: JmapEmailSubmissionQueryOptions,
659 ) -> Result<JmapEmailSubmissionQueryOutput, JmapClientStdError> {
660 let coroutine =
661 JmapEmailSubmissionQuery::new(self.session_or_err()?, &self.http_auth, opts)?;
662 self.run(coroutine)
663 }
664
665 pub fn email_submission_set(
667 &mut self,
668 submissions: BTreeMap<String, JmapEmailSubmissionCreate>,
669 ) -> Result<JmapEmailSubmissionSetOutput, JmapClientStdError> {
670 let coroutine =
671 JmapEmailSubmissionSet::new(self.session_or_err()?, &self.http_auth, submissions)?;
672 self.run(coroutine)
673 }
674
675 pub fn email_submission_cancel(
678 &mut self,
679 ids: Vec<String>,
680 ) -> Result<JmapEmailSubmissionCancelOutput, JmapClientStdError> {
681 let coroutine =
682 JmapEmailSubmissionCancel::new(self.session_or_err()?, &self.http_auth, ids)?;
683 self.run(coroutine)
684 }
685
686 pub fn vacation_response_get(
688 &mut self,
689 ) -> Result<Option<JmapVacationResponse>, JmapClientStdError> {
690 let coroutine = JmapVacationResponseGet::new(self.session_or_err()?, &self.http_auth)?;
691 Ok(self.run(coroutine)?.vacation_response)
692 }
693
694 pub fn vacation_response_set(
697 &mut self,
698 patch: JmapVacationResponseUpdate,
699 ) -> Result<Option<JmapVacationResponse>, JmapClientStdError> {
700 let coroutine =
701 JmapVacationResponseSet::new(self.session_or_err()?, &self.http_auth, patch)?;
702 Ok(self.run(coroutine)?.updated)
703 }
704
705 pub fn address_book_get(
707 &mut self,
708 opts: JmapAddressBookGetOptions,
709 ) -> Result<JmapAddressBookGetOutput, JmapClientStdError> {
710 let coroutine = JmapAddressBookGet::new(self.session_or_err()?, &self.http_auth, opts)?;
711 self.run(coroutine)
712 }
713
714 pub fn address_book_set(
716 &mut self,
717 args: JmapAddressBookSetArgs,
718 ) -> Result<JmapAddressBookSetOutput, JmapClientStdError> {
719 let coroutine = JmapAddressBookSet::new(self.session_or_err()?, &self.http_auth, args)?;
720 self.run(coroutine)
721 }
722
723 pub fn address_book_changes(
725 &mut self,
726 since_state: impl Into<String>,
727 opts: JmapAddressBookChangesOptions,
728 ) -> Result<JmapChangesOutput, JmapClientStdError> {
729 let coroutine = JmapAddressBookChanges::new(
730 self.session_or_err()?,
731 &self.http_auth,
732 since_state,
733 opts,
734 )?;
735 self.run(coroutine)
736 }
737
738 pub fn contact_card_get(
740 &mut self,
741 opts: JmapContactCardGetOptions,
742 ) -> Result<JmapContactCardGetOutput, JmapClientStdError> {
743 let coroutine = JmapContactCardGet::new(self.session_or_err()?, &self.http_auth, opts)?;
744 self.run(coroutine)
745 }
746
747 pub fn contact_card_query(
750 &mut self,
751 opts: JmapContactCardQueryOptions,
752 ) -> Result<JmapContactCardQueryOutput, JmapClientStdError> {
753 let coroutine = JmapContactCardQuery::new(self.session_or_err()?, &self.http_auth, opts)?;
754 self.run(coroutine)
755 }
756
757 pub fn contact_card_set(
759 &mut self,
760 args: JmapContactCardSetArgs,
761 ) -> Result<JmapContactCardSetOutput, JmapClientStdError> {
762 let coroutine = JmapContactCardSet::new(self.session_or_err()?, &self.http_auth, args)?;
763 self.run(coroutine)
764 }
765
766 pub fn contact_card_changes(
768 &mut self,
769 since_state: impl Into<String>,
770 opts: JmapContactCardChangesOptions,
771 ) -> Result<JmapChangesOutput, JmapClientStdError> {
772 let coroutine = JmapContactCardChanges::new(
773 self.session_or_err()?,
774 &self.http_auth,
775 since_state,
776 opts,
777 )?;
778 self.run(coroutine)
779 }
780
781 pub fn contact_card_copy(
783 &mut self,
784 from_account_id: impl Into<String>,
785 cards: BTreeMap<String, JmapContactCardCopyArgs>,
786 ) -> Result<JmapContactCardCopyOutput, JmapClientStdError> {
787 let coroutine = JmapContactCardCopy::new(
788 self.session_or_err()?,
789 &self.http_auth,
790 from_account_id,
791 cards,
792 )?;
793 self.run(coroutine)
794 }
795}
796
797impl fmt::Debug for JmapClientStd {
798 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
799 f.debug_struct("JmapClientStd")
800 .field("http_auth", &self.http_auth)
801 .field("session", &self.session)
802 .finish_non_exhaustive()
803 }
804}
805
806pub trait JmapStream: Read + Write + Send + Any {
812 fn as_any_mut(&mut self) -> &mut dyn Any;
814}
815
816impl<T: Read + Write + Send + Any> JmapStream for T {
817 fn as_any_mut(&mut self) -> &mut dyn Any {
818 self
819 }
820}