1use std::future::{Future, IntoFuture};
44use std::marker::PhantomData;
45use std::pin::Pin;
46use std::sync::Arc;
47
48use std::any::TypeId;
49
50use leptos::prelude::*;
51use leptos::server::codee::{Decoder, Encoder};
52use serde::{de::DeserializeOwned, Serialize};
53
54use crate::api::error::ApiError;
55
56pub struct JsonSliceCodec;
58
59impl<T: Serialize> Encoder<T> for JsonSliceCodec {
60 type Error = serde_json::Error;
61 type Encoded = String;
62
63 fn encode(val: &T) -> Result<Self::Encoded, Self::Error> {
64 serde_json::to_string(val)
65 }
66}
67
68impl<T: DeserializeOwned> Decoder<T> for JsonSliceCodec {
69 type Error = serde_json::Error;
70 type Encoded = str;
71
72 fn decode(val: &Self::Encoded) -> Result<T, Self::Error> {
73 serde_json::from_slice(val.as_bytes())
74 }
75}
76
77#[derive(Clone)]
80pub struct RawJson {
81 text: String,
82 #[cfg(not(target_arch = "wasm32"))]
83 value: Option<Arc<dyn std::any::Any + Send + Sync>>,
84}
85
86impl RawJson {
87 fn from_text(text: String) -> Self {
88 Self {
89 text,
90 #[cfg(not(target_arch = "wasm32"))]
91 value: None,
92 }
93 }
94}
95
96impl PartialEq for RawJson {
97 fn eq(&self, other: &Self) -> bool {
98 self.text == other.text
99 }
100}
101
102impl std::fmt::Debug for RawJson {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 f.debug_tuple("RawJson").field(&self.text).finish()
105 }
106}
107
108pub struct RawJsonCodec;
110
111impl Encoder<RawJson> for RawJsonCodec {
112 type Error = std::convert::Infallible;
113 type Encoded = String;
114
115 fn encode(val: &RawJson) -> Result<Self::Encoded, Self::Error> {
116 Ok(val.text.clone())
117 }
118}
119
120impl Decoder<RawJson> for RawJsonCodec {
121 type Error = std::convert::Infallible;
122 type Encoded = str;
123
124 fn decode(val: &Self::Encoded) -> Result<RawJson, Self::Error> {
125 Ok(RawJson::from_text(val.to_owned()))
126 }
127}
128
129pub struct PageJson<T> {
131 raw: RawJson,
132 _type: PhantomData<fn() -> T>,
133}
134
135fn encode_page_json<T: Serialize>(result: &Result<T, ApiError>) -> String {
136 serde_json::to_string(result).unwrap_or_else(|err| {
137 let failed: Result<(), ApiError> = Err(ApiError::Framework(ServerFnError::Serialization(
138 err.to_string(),
139 )));
140 serde_json::to_string(&failed).expect("ApiError 可序列化")
141 })
142}
143
144impl<T: Serialize + Send + Sync + 'static> PageJson<T> {
145 pub fn new(result: Result<T, ApiError>) -> Self {
147 let text = encode_page_json(&result);
148 Self::with_text(text, result)
149 }
150
151 pub fn with_hydration(result: Result<T, ApiError>, hydration: impl FnOnce(&T) -> T) -> Self {
156 #[cfg(not(target_arch = "wasm32"))]
157 let text = match &result {
158 Ok(value) => encode_page_json(&Ok::<T, ApiError>(hydration(value))),
159 Err(_) => encode_page_json(&result),
160 };
161 #[cfg(target_arch = "wasm32")]
162 let text = {
163 let _ = hydration;
164 encode_page_json(&result)
165 };
166 Self::with_text(text, result)
167 }
168
169 fn with_text(text: String, result: Result<T, ApiError>) -> Self {
170 #[cfg(target_arch = "wasm32")]
171 let _ = result;
172 Self {
173 raw: RawJson {
174 text,
175 #[cfg(not(target_arch = "wasm32"))]
176 value: Some(Arc::new(result)),
177 },
178 _type: PhantomData,
179 }
180 }
181}
182
183pub struct RouteData<T> {
185 raw: Resource<RawJson, RawJsonCodec>,
186 _type: PhantomData<fn() -> T>,
187}
188
189impl<T> Clone for RouteData<T> {
190 fn clone(&self) -> Self {
191 *self
192 }
193}
194
195impl<T> Copy for RouteData<T> {}
196
197impl<T: DeserializeOwned + Clone + Send + Sync + 'static> IntoFuture for RouteData<T> {
198 type Output = Result<T, ApiError>;
199 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
200
201 fn into_future(self) -> Self::IntoFuture {
202 let raw = self.raw;
203 Box::pin(async move { resolve_page_json(&raw.await) })
204 }
205}
206
207fn resolve_page_json<T: DeserializeOwned + Clone + 'static>(raw: &RawJson) -> Result<T, ApiError> {
209 #[cfg(not(target_arch = "wasm32"))]
210 if let Some(result) = raw
211 .value
212 .as_deref()
213 .and_then(|value| value.downcast_ref::<Result<T, ApiError>>())
214 {
215 return result.clone();
216 }
217 serde_json::from_slice::<Result<T, ApiError>>(raw.text.as_bytes()).unwrap_or_else(|err| {
218 Err(ApiError::Framework(ServerFnError::Deserialization(
219 err.to_string(),
220 )))
221 })
222}
223
224pub fn unit<F, Fut>(fetcher: F) -> impl Fn(()) -> Fut + Send + Sync + Copy + 'static
227where
228 F: Fn() -> Fut + Send + Sync + Copy + 'static,
229{
230 move |()| fetcher()
231}
232
233#[track_caller]
242pub fn route_data<S, T, F, Fut>(
243 source: impl Fn() -> S + Send + Sync + 'static,
244 fetcher: F,
245) -> RouteData<T>
246where
247 S: PartialEq + Clone + Send + Sync + 'static,
248 F: Fn(S) -> Fut + Send + Sync + 'static,
249 Fut: Future<Output = PageJson<T>> + Send + 'static,
250{
251 let source: ErasedSource = Arc::new(move || ErasedKey(Arc::new(source())));
252 let fetch: ErasedFetch = Arc::new(move |key: ErasedKey| {
253 let source: S = key.downcast::<S>();
254 #[cfg(target_arch = "wasm32")]
255 {
256 let pending = match PREFETCH.take(TypeId::of::<F>(), &source, now_ms()) {
257 Some(hit) => Ok(hit),
258 None => Err(fetcher(source)),
259 };
260 Box::pin(async move {
261 match pending {
262 Ok(hit) => hit.await,
263 Err(fut) => fut.await.raw,
264 }
265 }) as RawFuture
266 }
267 #[cfg(not(target_arch = "wasm32"))]
268 {
269 let fut = fetcher(source);
270 Box::pin(async move { fut.await.raw }) as RawFuture
271 }
272 });
273 #[cfg(not(target_arch = "wasm32"))]
274 if let Some(registry) = use_context::<RouteDataRegistry>() {
275 let key = untrack(|| source());
276 let mut entries = registry.0.lock().unwrap_or_else(|e| e.into_inner());
277 let slot = entries
278 .iter()
279 .position(|(tid, k, _)| *tid == TypeId::of::<F>() && *k == key);
280 let raw = match slot {
281 Some(i) if !entries[i].2.is_disposed() => entries[i].2,
282 Some(i) => {
284 let raw = erased_route_resource(source, fetch);
285 entries[i].2 = raw;
286 raw
287 }
288 None => {
289 let raw = erased_route_resource(source, fetch);
290 entries.push((TypeId::of::<F>(), key, raw));
291 raw
292 }
293 };
294 return RouteData {
295 raw,
296 _type: PhantomData,
297 };
298 }
299 RouteData {
300 raw: erased_route_resource(source, fetch),
301 _type: PhantomData,
302 }
303}
304
305#[cfg(not(target_arch = "wasm32"))]
311#[derive(Clone, Default)]
312struct RouteDataRegistry(Arc<std::sync::Mutex<Vec<RegistryEntry>>>);
313
314#[cfg(not(target_arch = "wasm32"))]
316type RegistryEntry = (TypeId, ErasedKey, Resource<RawJson, RawJsonCodec>);
317
318pub fn provide_route_data_registry() {
320 #[cfg(not(target_arch = "wasm32"))]
321 provide_context(RouteDataRegistry::default());
322}
323
324trait DynKey: Send + Sync {
326 fn as_any(&self) -> &dyn std::any::Any;
327 fn eq_dyn(&self, other: &dyn DynKey) -> bool;
328}
329
330impl<S: PartialEq + Send + Sync + 'static> DynKey for S {
331 fn as_any(&self) -> &dyn std::any::Any {
332 self
333 }
334
335 fn eq_dyn(&self, other: &dyn DynKey) -> bool {
336 other.as_any().downcast_ref::<S>() == Some(self)
337 }
338}
339
340#[derive(Clone)]
341struct ErasedKey(Arc<dyn DynKey>);
342
343impl ErasedKey {
344 fn downcast<S: Clone + 'static>(&self) -> S {
347 self.0
348 .as_any()
349 .downcast_ref::<S>()
350 .expect("路由数据键类型一致")
351 .clone()
352 }
353}
354
355impl PartialEq for ErasedKey {
356 fn eq(&self, other: &Self) -> bool {
357 self.0.eq_dyn(&*other.0)
358 }
359}
360
361type RawFuture = Pin<Box<dyn Future<Output = RawJson> + Send>>;
362type ErasedSource = Arc<dyn Fn() -> ErasedKey + Send + Sync>;
363type ErasedFetch = Arc<dyn Fn(ErasedKey) -> RawFuture + Send + Sync>;
364
365#[track_caller]
367#[inline(never)]
368fn erased_route_resource(
369 source: ErasedSource,
370 fetch: ErasedFetch,
371) -> Resource<RawJson, RawJsonCodec> {
372 Resource::new_with_options(move || source(), move |key| fetch(key), false)
373}
374
375pub fn prefetch_route_data<S, T, F, Fut>(source: S, fetcher: F)
378where
379 S: PartialEq + Clone + Send + 'static,
380 F: Fn(S) -> Fut + 'static,
381 Fut: Future<Output = PageJson<T>> + Send + 'static,
382{
383 #[cfg(target_arch = "wasm32")]
384 {
385 let Some(id) = PREFETCH.begin(TypeId::of::<F>(), source.clone(), now_ms()) else {
386 return;
387 };
388 let fut = fetcher(source);
389 leptos::task::spawn_local(async move {
390 let raw = fut.await.raw;
391 PREFETCH.complete(id, raw, now_ms());
392 });
393 }
394 #[cfg(not(target_arch = "wasm32"))]
395 let _ = (source, fetcher);
396}
397
398#[cfg(target_arch = "wasm32")]
399static PREFETCH: prefetch::Store = prefetch::Store::new();
400
401#[cfg(target_arch = "wasm32")]
402fn now_ms() -> f64 {
403 js_sys::Date::now()
404}
405
406#[cfg(any(target_arch = "wasm32", test))]
408mod prefetch {
409 use super::RawJson;
410 use std::any::{Any, TypeId};
411 use std::future::Future;
412 use std::pin::Pin;
413 use std::sync::{Arc, Mutex};
414 use std::task::{Context, Poll, Waker};
415
416 pub(super) const TTL_MS: f64 = 10_000.0;
418 pub(super) const CAPACITY: usize = 24;
419
420 enum Slot {
421 Pending(Vec<Waker>),
422 Ready(RawJson),
423 }
424
425 struct Entry {
426 id: u64,
427 fetcher: TypeId,
428 source: Box<dyn Any + Send>,
429 done_at: Option<f64>,
431 taken: bool,
433 slot: Arc<Mutex<Slot>>,
434 }
435
436 impl Entry {
437 fn matches<S: PartialEq + 'static>(&self, fetcher: TypeId, source: &S) -> bool {
438 !self.taken
439 && self.fetcher == fetcher
440 && self.source.downcast_ref::<S>().is_some_and(|s| s == source)
441 }
442
443 fn expired(&self, now: f64) -> bool {
444 self.done_at.is_some_and(|t| now - t > TTL_MS)
445 }
446 }
447
448 struct Inner {
449 next_id: u64,
450 entries: Vec<Entry>,
451 }
452
453 pub(super) struct Store {
454 inner: Mutex<Inner>,
455 }
456
457 impl Store {
458 pub(super) const fn new() -> Self {
459 Self {
460 inner: Mutex::new(Inner {
461 next_id: 0,
462 entries: Vec::new(),
463 }),
464 }
465 }
466
467 fn lock(&self) -> std::sync::MutexGuard<'_, Inner> {
468 self.inner.lock().unwrap_or_else(|e| e.into_inner())
469 }
470
471 pub(super) fn begin<S: PartialEq + Send + 'static>(
476 &self,
477 fetcher: TypeId,
478 source: S,
479 now: f64,
480 ) -> Option<u64> {
481 let mut inner = self.lock();
482 inner.entries.retain(|e| !e.expired(now));
483 if inner.entries.iter().any(|e| e.matches(fetcher, &source)) {
484 return None;
485 }
486 if inner.entries.len() >= CAPACITY {
487 let oldest_done = inner.entries.iter().position(|e| e.done_at.is_some())?;
488 inner.entries.remove(oldest_done);
489 }
490 let id = inner.next_id;
491 inner.next_id += 1;
492 inner.entries.push(Entry {
493 id,
494 fetcher,
495 source: Box::new(source),
496 done_at: None,
497 taken: false,
498 slot: Arc::new(Mutex::new(Slot::Pending(Vec::new()))),
499 });
500 Some(id)
501 }
502
503 pub(super) fn complete(&self, id: u64, raw: RawJson, now: f64) {
506 let slot = {
507 let mut inner = self.lock();
508 let Some(index) = inner.entries.iter().position(|e| e.id == id) else {
509 return;
510 };
511 let entry = &mut inner.entries[index];
512 if entry.taken || is_err_json(&raw) {
513 inner.entries.remove(index).slot
514 } else {
515 entry.done_at = Some(now);
516 entry.slot.clone()
517 }
518 };
519 let wakers = {
520 let mut slot = slot.lock().unwrap_or_else(|e| e.into_inner());
521 match std::mem::replace(&mut *slot, Slot::Ready(raw)) {
522 Slot::Pending(wakers) => wakers,
523 Slot::Ready(_) => Vec::new(),
524 }
525 };
526 for waker in wakers {
527 waker.wake();
528 }
529 }
530
531 pub(super) fn take<S: PartialEq + 'static>(
533 &self,
534 fetcher: TypeId,
535 source: &S,
536 now: f64,
537 ) -> Option<Prefetched> {
538 let mut inner = self.lock();
539 let index = inner
540 .entries
541 .iter()
542 .position(|e| e.matches(fetcher, source))?;
543 if inner.entries[index].expired(now) {
544 inner.entries.remove(index);
545 return None;
546 }
547 if inner.entries[index].done_at.is_some() {
548 return Some(Prefetched(inner.entries.remove(index).slot));
549 }
550 let entry = &mut inner.entries[index];
551 entry.taken = true;
552 Some(Prefetched(entry.slot.clone()))
553 }
554 }
555
556 pub(super) struct Prefetched(Arc<Mutex<Slot>>);
558
559 impl Future for Prefetched {
560 type Output = RawJson;
561
562 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<RawJson> {
563 let mut slot = self.0.lock().unwrap_or_else(|e| e.into_inner());
564 match &mut *slot {
565 Slot::Ready(raw) => Poll::Ready(raw.clone()),
566 Slot::Pending(wakers) => {
567 if !wakers.iter().any(|w| w.will_wake(cx.waker())) {
568 wakers.push(cx.waker().clone());
569 }
570 Poll::Pending
571 }
572 }
573 }
574 }
575
576 pub(super) fn is_err_json(raw: &RawJson) -> bool {
579 raw.text.starts_with("{\"Err\"")
580 }
581}
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586 use crate::utils::error::is_not_found;
587
588 #[derive(Debug, Clone, PartialEq, Serialize, serde::Deserialize)]
589 struct Sample {
590 id: i32,
591 title: String,
592 }
593
594 #[test]
595 fn json_slice_codec_round_trips() {
596 let value = Sample {
597 id: 7,
598 title: "中文标题 \"quoted\"".into(),
599 };
600 let encoded = <JsonSliceCodec as Encoder<Sample>>::encode(&value).unwrap();
601 let decoded: Sample = <JsonSliceCodec as Decoder<Sample>>::decode(&encoded).unwrap();
602 assert_eq!(decoded, value);
603 }
604
605 #[test]
607 fn page_json_survives_resource_round_trip() {
608 let through_resource = |page: PageJson<Sample>| {
609 let wire = <RawJsonCodec as Encoder<RawJson>>::encode(&page.raw).unwrap();
610 <RawJsonCodec as Decoder<RawJson>>::decode(&wire).unwrap()
611 };
612
613 let ok = through_resource(PageJson::new(Ok(Sample {
614 id: 1,
615 title: "标题".into(),
616 })));
617 assert_eq!(
618 resolve_page_json::<Sample>(&ok).unwrap(),
619 Sample {
620 id: 1,
621 title: "标题".into()
622 }
623 );
624
625 let missing = through_resource(PageJson::new(Err(ApiError::NotFound("文章不存在".into()))));
626 let error = resolve_page_json::<Sample>(&missing).unwrap_err();
627 assert!(matches!(&error, ApiError::NotFound(message) if message == "文章不存在"));
628 assert!(is_not_found(&error));
629 }
630
631 #[test]
633 fn mismatched_page_json_is_a_deserialization_error() {
634 let page = PageJson::new(Ok("not a sample".to_string()));
635 assert!(matches!(
636 resolve_page_json::<Sample>(&page.raw),
637 Err(ApiError::Framework(ServerFnError::Deserialization(_)))
638 ));
639 }
640
641 #[test]
643 fn hydration_form_only_reaches_the_wire() {
644 let full = Sample {
645 id: 2,
646 title: "很长的正文".repeat(100),
647 };
648 let slim = |sample: &Sample| Sample {
649 id: sample.id,
650 title: String::new(),
651 };
652 let page = PageJson::with_hydration(Ok(full.clone()), slim);
653 assert_eq!(resolve_page_json::<Sample>(&page.raw).unwrap(), full);
654 let wire = <RawJsonCodec as Encoder<RawJson>>::encode(&page.raw).unwrap();
655 let hydrated = <RawJsonCodec as Decoder<RawJson>>::decode(&wire).unwrap();
656 assert_eq!(resolve_page_json::<Sample>(&hydrated).unwrap(), slim(&full));
657
658 let missing = PageJson::with_hydration(Err(ApiError::NotFound("没有".into())), slim);
659 let wire = <RawJsonCodec as Encoder<RawJson>>::encode(&missing.raw).unwrap();
660 let hydrated = <RawJsonCodec as Decoder<RawJson>>::decode(&wire).unwrap();
661 assert!(is_not_found(
662 &resolve_page_json::<Sample>(&hydrated).unwrap_err()
663 ));
664 }
665
666 async fn fetch_a() -> PageJson<u8> {
667 PageJson::new(Ok(1))
668 }
669
670 async fn fetch_b() -> PageJson<u8> {
671 PageJson::new(Ok(2))
672 }
673
674 fn key_of<F: 'static>(_: &F) -> std::any::TypeId {
675 std::any::TypeId::of::<F>()
676 }
677
678 #[test]
680 fn unit_keys_follow_the_function() {
681 assert_eq!(key_of(&unit(fetch_a)), key_of(&unit(fetch_a)));
682 assert_ne!(key_of(&unit(fetch_a)), key_of(&unit(fetch_b)));
683 }
684
685 mod store {
686 use super::*;
687 use crate::hooks::ssr_resource::prefetch::{
688 is_err_json, Prefetched, Store, CAPACITY, TTL_MS,
689 };
690 use std::any::TypeId;
691 use std::task::{Context, Poll, Waker};
692
693 const KEY: fn() -> TypeId = TypeId::of::<u8>;
694 const OTHER_KEY: fn() -> TypeId = TypeId::of::<u16>;
695
696 fn ok(n: u8) -> RawJson {
697 PageJson::new(Ok(n)).raw
698 }
699
700 fn poll(hit: &mut Prefetched) -> Poll<RawJson> {
701 Pin::new(hit).poll(&mut Context::from_waker(Waker::noop()))
702 }
703
704 #[test]
705 fn begin_deduplicates_by_key_and_source() {
706 let store = Store::new();
707 assert!(store.begin(KEY(), 1, 0.0).is_some());
708 assert!(store.begin(KEY(), 1, 0.0).is_none());
709 assert!(store.begin(KEY(), 2, 0.0).is_some());
710 assert!(store.begin(OTHER_KEY(), 1, 0.0).is_some());
711 }
712
713 #[test]
714 fn pending_entry_resolves_for_a_waiting_consumer() {
715 let store = Store::new();
716 let id = store.begin(KEY(), 1, 0.0).unwrap();
717 let mut hit = store.take(KEY(), &1, 5.0).unwrap();
718 assert!(poll(&mut hit).is_pending());
719 store.complete(id, ok(7), 10.0);
720 assert_eq!(poll(&mut hit), Poll::Ready(ok(7)));
721 assert!(store.take(KEY(), &1, 11.0).is_none());
723 assert!(store.begin(KEY(), 1, 11.0).is_some());
724 }
725
726 #[test]
727 fn taken_pending_entry_no_longer_deduplicates() {
728 let store = Store::new();
729 store.begin(KEY(), 1, 0.0).unwrap();
730 store.take(KEY(), &1, 0.0).unwrap();
731 assert!(store.begin(KEY(), 1, 0.0).is_some());
732 }
733
734 #[test]
735 fn completed_entry_is_consumed_once() {
736 let store = Store::new();
737 let id = store.begin(KEY(), 1, 0.0).unwrap();
738 store.complete(id, ok(3), 1.0);
739 let mut hit = store.take(KEY(), &1, 2.0).unwrap();
740 assert_eq!(poll(&mut hit), Poll::Ready(ok(3)));
741 assert!(store.take(KEY(), &1, 2.0).is_none());
742 }
743
744 #[test]
745 fn completed_entry_expires_after_ttl_but_pending_does_not() {
746 let store = Store::new();
747 let fresh = store.begin(KEY(), 1, 0.0).unwrap();
748 store.complete(fresh, ok(1), 100.0);
749 assert!(store.take(KEY(), &1, 100.0 + TTL_MS - 1.0).is_some());
750
751 let stale = store.begin(KEY(), 2, 0.0).unwrap();
752 store.complete(stale, ok(2), 100.0);
753 assert!(store.take(KEY(), &2, 100.0 + TTL_MS + 1.0).is_none());
754 assert!(store.begin(KEY(), 2, 100.0 + TTL_MS + 1.0).is_some());
755
756 store.begin(KEY(), 3, 0.0).unwrap();
757 assert!(store.take(KEY(), &3, TTL_MS * 10.0).is_some());
758 }
759
760 #[test]
761 fn error_results_are_not_cached_but_reach_waiters() {
762 let store = Store::new();
763 let id = store.begin(KEY(), 1, 0.0).unwrap();
764 let mut waiter = store.take(KEY(), &1, 0.0).unwrap();
765 let id2 = store.begin(KEY(), 2, 0.0).unwrap();
766 let mut early = store.take(KEY(), &2, 0.0).unwrap();
767 let failure = PageJson::<u8>::new(Err(ApiError::Internal("失败".into()))).raw;
768 store.complete(id, failure.clone(), 1.0);
769 assert_eq!(poll(&mut waiter), Poll::Ready(failure.clone()));
770 store.complete(id2, failure.clone(), 1.0);
771 assert_eq!(poll(&mut early), Poll::Ready(failure));
772 assert!(store.take(KEY(), &1, 2.0).is_none());
773 assert!(store.begin(KEY(), 1, 2.0).is_some());
774
775 let id3 = store.begin(KEY(), 3, 0.0).unwrap();
777 store.complete(
778 id3,
779 PageJson::<u8>::new(Err(ApiError::Internal("失败".into()))).raw,
780 1.0,
781 );
782 assert!(store.take(KEY(), &3, 2.0).is_none());
783 }
784
785 #[test]
786 fn capacity_evicts_the_oldest_completed_entry() {
787 let store = Store::new();
788 for source in 0..=CAPACITY as i32 {
789 let id = store.begin(KEY(), source, 0.0).unwrap();
790 store.complete(id, ok(1), 0.0);
791 }
792 assert!(store.take(KEY(), &0, 1.0).is_none());
793 assert!(store.take(KEY(), &1, 1.0).is_some());
794 assert!(store.take(KEY(), &(CAPACITY as i32), 1.0).is_some());
795 }
796
797 #[test]
798 fn capacity_full_of_pending_requests_refuses_new_ones() {
799 let store = Store::new();
800 for source in 0..CAPACITY as i32 {
801 assert!(store.begin(KEY(), source, 0.0).is_some());
802 }
803 assert!(store.begin(KEY(), CAPACITY as i32, 0.0).is_none());
804 }
805
806 #[test]
808 fn error_encoding_is_recognised() {
809 assert!(is_err_json(
810 &PageJson::<u8>::new(Err(ApiError::Internal("失败".into()))).raw
811 ));
812 assert!(!is_err_json(&PageJson::new(Ok(1u8)).raw));
813 }
814 }
815}