1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
#![allow(unused)]
use std::{error::Error, marker::PhantomData};
use async_trait::async_trait;
use serde::{de::DeserializeOwned, Serialize};
use worker::{Env, State, Stub};
use crate::transport::{RequestTransport, ResponseTransport};
/// A request sent to an object.
pub enum ProxiedRequest<R> {
Fetch(R),
Alarm,
}
/// The [`DoProxy`] trait is the main interface of this library. Implement this
/// trait for a type you want to make into a durable object and automatically
/// get many helper methods for interacting with it.
///
/// After implementing this trait, use the macro `do_proxy!` to generate the
/// workers-rs [`worker::DurableObject`] glue code.
///
/// See the crates under `examples/*` for example implementations.
#[async_trait(?Send)]
pub trait DoProxy
where
Self: Sized,
{
/// The Durable Object's binding. Must be the same as the one written in
/// your `wrangler.toml`. For example, `INSERTER_OBJECT`.
const BINDING: &'static str;
/// The initialization data that will be passed to the the object when it is
/// first created. This should be used to set data that is expected to
/// always be available when the object loads. For example, the first time a
/// `Person` object is created, it should be initialized with the person's
/// name. This way when `load_from_storage` is called, we can expect a
/// stored `name` field to always be present. If it is not present, then the
/// object has not yet been initialized and `load_from_storage` should
/// error.
///
/// # Example
///
/// ```ignore
/// struct NewPerson {
/// name: String,
/// birthday: DateTime<Utc>,
/// }
/// ```
type Init: Serialize + DeserializeOwned + 'static;
/// The request type that will be sent to the object. This is generally an
/// enum of all of the different "commands" that the object can handle.
///
/// # Example
///
/// ```ignore
/// enum PersonRequest {
/// GetAge,
/// GetNextBirthday,
/// GetName,
/// }
/// ```
type Request: Serialize + DeserializeOwned + 'static;
/// The response type that will be sent back from the object This is generally
/// an enum of all of the different "responses" that the object can send.
///
/// Note, types like `Option<serde_json::Value>` will work!
///
/// # Example
///
/// ```ignore
/// enum PersonResponse {
/// Age(usize),
/// Birthday(chrono::DateTime<chrono::Utc>),
/// GetName(String),
/// }
/// ```
type Response: Serialize + DeserializeOwned + 'static;
/// The error type that will be returned from the object. This lets users
/// cleanly (kind of) pass errors from the object back to the caller.
///
/// # Example
///
/// ```ignore
/// enum PersonError {
/// NotYetBorn,
/// }
/// ```
type Error: Serialize + DeserializeOwned + Error;
/// Called if the object is sent an `init` request. This function may be
/// called multiple times and implemeting it is _optional_.
async fn init(ctx: &mut Ctx, init: Self::Init) -> Result<(), Self::Error> {
Ok(())
}
/// Called when the object is first loaded into memory. This function is
/// generally called only once when the Durable Object first receives a
/// request. If the object is evicted from memory and then later receives a
/// request, this function will be called again.
async fn load_from_storage(ctx: &mut Ctx) -> Result<Self, Self::Error>;
/// Called when the object receives a fetch request or an alarm. This is
/// generally where you would match on [`Self::Request`] and call the
/// appropriate function.
async fn handle(
&mut self,
ctx: &mut Ctx,
req: ProxiedRequest<Self::Request>,
) -> Result<Self::Response, Self::Error>;
/// This function wraps the `handle` function and handles the boilerplate of
/// caching, converting between the different transport types, and error
/// handling.
///
/// You should never implement this function, however you can if you need
/// to.
async fn run_request(
cached_proxy: &mut Option<Self>,
ctx: &mut Ctx,
req: Option<worker::Request>,
) -> worker::Result<worker::Response> {
enum TransportOrAlarm<Init, Request> {
Transport(RequestTransport<Init, Request>),
Alarm,
}
let mut transport_or_alarm: TransportOrAlarm<Self::Init, Self::Request> = match req {
Some(mut req) => TransportOrAlarm::Transport(req.json().await?),
None => TransportOrAlarm::Alarm,
};
let mut proxy = match cached_proxy.take() {
Some(proxy) => proxy,
None => {
if let Some(init) = match transport_or_alarm {
TransportOrAlarm::Transport(ref mut transport) => transport.take_init(),
TransportOrAlarm::Alarm => None,
} {
Self::init(ctx, init).await.map_err(|e| e.to_string())?;
Self::load_from_storage(ctx)
.await
.map_err(|e| e.to_string())?
} else {
Self::load_from_storage(ctx)
.await
.map_err(|e| e.to_string())?
}
}
};
let response = match transport_or_alarm {
TransportOrAlarm::Transport(RequestTransport::Request { request }) => {
match proxy.handle(ctx, ProxiedRequest::Fetch(request)).await {
Ok(response) => ResponseTransport::Response { response },
Err(error) => ResponseTransport::Error { error },
}
}
TransportOrAlarm::Alarm => match proxy.handle(ctx, ProxiedRequest::Alarm).await {
Ok(response) => ResponseTransport::Response { response },
Err(error) => ResponseTransport::Error { error },
},
TransportOrAlarm::Transport(RequestTransport::Empty) => ResponseTransport::Initialized,
_ => {
unreachable!("RequestTransport::Init and RequestTransport::InitWithRequest should have been handled by the match arm above");
}
};
*cached_proxy = Some(proxy);
worker::Response::from_json(&response)
}
}
/// The context that is passed to the object's `init`, `load_from_storage`, and `handle` functions.
///
/// Wraps [`worker::State`] and [`worker::Env`].
pub struct Ctx<'s> {
pub state: &'s State,
pub env: &'s Env,
}
impl<'s> Ctx<'s> {
pub fn new(state: &'s State, env: &'s Env) -> Self {
Self { state, env }
}
}