From b2fdbafa6719838243468c3148fb6105e5443630 Mon Sep 17 00:00:00 2001 From: Francesco Guardiani Date: Thu, 22 Aug 2024 11:46:45 +0200 Subject: [PATCH] Rust docs (#17) * Rust docs * Fix doc test --- README.md | 2 +- justfile | 5 +- macros/src/ast.rs | 4 +- macros/src/lib.rs | 7 ++- src/context/mod.rs | 129 +++++++++++++++++++++++++++++++++++++++- src/context/request.rs | 5 ++ src/context/run.rs | 2 +- src/discovery.rs | 2 + src/endpoint/context.rs | 3 + src/endpoint/mod.rs | 15 +++++ src/errors.rs | 12 ++++ src/http_server.rs | 11 ++++ src/hyper.rs | 3 + src/lib.rs | 39 ++++++++++++ src/serde.rs | 16 +++++ src/service.rs | 7 +++ 16 files changed, 254 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1fd390d..5b6a122 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Documentation](https://img.shields.io/badge/doc-reference-blue)](https://docs.restate.dev) +[![Documentation](https://img.shields.io/docsrs/restate-sdk)](https://docs.rs/restate-sdk) [![crates.io](https://img.shields.io/crates/v/restate_sdk.svg)](https://crates.io/crates/restate-sdk/) [![Examples](https://img.shields.io/badge/view-examples-blue)](https://github.com/restatedev/examples) [![Discord](https://img.shields.io/discord/1128210118216007792?logo=discord)](https://discord.gg/skW3AZ6uGd) diff --git a/justfile b/justfile index b5c144c..c9f0df3 100644 --- a/justfile +++ b/justfile @@ -66,8 +66,11 @@ print-target: test: (_target-installed target) cargo nextest run {{ _target-option }} --all-features +doctest: + cargo test --doc + # Runs lints and tests -verify: lint test +verify: lint test doctest udeps *flags: RUSTC_BOOTSTRAP=1 cargo udeps --all-features --all-targets {{ flags }} diff --git a/macros/src/ast.rs b/macros/src/ast.rs index b485b64..9756762 100644 --- a/macros/src/ast.rs +++ b/macros/src/ast.rs @@ -8,8 +8,8 @@ // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0. -//! Some parts copied from https://github.com/dtolnay/thiserror/blob/39aaeb00ff270a49e3c254d7b38b10e934d3c7a5/impl/src/ast.rs -//! License Apache-2.0 or MIT +// Some parts copied from https://github.com/dtolnay/thiserror/blob/39aaeb00ff270a49e3c254d7b38b10e934d3c7a5/impl/src/ast.rs +// License Apache-2.0 or MIT use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream}; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 814c7f3..501273f 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -8,8 +8,8 @@ // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0. -//! Some parts of this codebase were taken from https://github.com/google/tarpc/blob/b826f332312d3702667880a464e247556ad7dbfe/plugins/src/lib.rs -//! License MIT +// Some parts of this codebase were taken from https://github.com/google/tarpc/blob/b826f332312d3702667880a464e247556ad7dbfe/plugins/src/lib.rs +// License MIT extern crate proc_macro; @@ -22,6 +22,7 @@ use proc_macro::TokenStream; use quote::ToTokens; use syn::parse_macro_input; +/// Entry-point macro to define a Restate service. #[proc_macro_attribute] pub fn service(_: TokenStream, input: TokenStream) -> TokenStream { let svc = parse_macro_input!(input as Service); @@ -31,6 +32,7 @@ pub fn service(_: TokenStream, input: TokenStream) -> TokenStream { .into() } +/// Entry-point macro to define a Restate object. #[proc_macro_attribute] pub fn object(_: TokenStream, input: TokenStream) -> TokenStream { let svc = parse_macro_input!(input as Object); @@ -40,6 +42,7 @@ pub fn object(_: TokenStream, input: TokenStream) -> TokenStream { .into() } +/// Entry-point macro to define a Restate workflow. #[proc_macro_attribute] pub fn workflow(_: TokenStream, input: TokenStream) -> TokenStream { let svc = parse_macro_input!(input as Workflow); diff --git a/src/context/mod.rs b/src/context/mod.rs index d785599..5ac8084 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -1,3 +1,5 @@ +//! Types exposing Restate functionalities to service handlers. + use crate::endpoint::{ContextInternal, InputMetadata}; use crate::errors::{HandlerResult, TerminalError}; use crate::serde::{Deserialize, Serialize}; @@ -11,6 +13,7 @@ pub use request::{Request, RequestTarget}; pub use run::RunClosure; pub type HeaderMap = http::HeaderMap; +/// Service handler context. pub struct Context<'ctx> { random_seed: u64, #[cfg(feature = "rand")] @@ -20,10 +23,12 @@ pub struct Context<'ctx> { } impl<'ctx> Context<'ctx> { + /// Get request headers. pub fn headers(&self) -> &HeaderMap { &self.headers } + /// Get request headers. pub fn headers_mut(&mut self) -> &HeaderMap { &mut self.headers } @@ -41,6 +46,7 @@ impl<'ctx> From<(&'ctx ContextInternal, InputMetadata)> for Context<'ctx> { } } +/// Object shared handler context. pub struct SharedObjectContext<'ctx> { key: String, random_seed: u64, @@ -51,14 +57,17 @@ pub struct SharedObjectContext<'ctx> { } impl<'ctx> SharedObjectContext<'ctx> { + /// Get object key. pub fn key(&self) -> &str { &self.key } + /// Get request headers. pub fn headers(&self) -> &HeaderMap { &self.headers } + /// Get request headers. pub fn headers_mut(&mut self) -> &HeaderMap { &mut self.headers } @@ -77,6 +86,7 @@ impl<'ctx> From<(&'ctx ContextInternal, InputMetadata)> for SharedObjectContext< } } +/// Object handler context. pub struct ObjectContext<'ctx> { key: String, random_seed: u64, @@ -87,14 +97,17 @@ pub struct ObjectContext<'ctx> { } impl<'ctx> ObjectContext<'ctx> { + /// Get object key. pub fn key(&self) -> &str { &self.key } + /// Get request headers. pub fn headers(&self) -> &HeaderMap { &self.headers } + /// Get request headers. pub fn headers_mut(&mut self) -> &HeaderMap { &mut self.headers } @@ -113,6 +126,7 @@ impl<'ctx> From<(&'ctx ContextInternal, InputMetadata)> for ObjectContext<'ctx> } } +/// Workflow shared handler context. pub struct SharedWorkflowContext<'ctx> { key: String, random_seed: u64, @@ -136,19 +150,23 @@ impl<'ctx> From<(&'ctx ContextInternal, InputMetadata)> for SharedWorkflowContex } impl<'ctx> SharedWorkflowContext<'ctx> { + /// Get workflow key. pub fn key(&self) -> &str { &self.key } + /// Get request headers. pub fn headers(&self) -> &HeaderMap { &self.headers } + /// Get request headers. pub fn headers_mut(&mut self) -> &HeaderMap { &mut self.headers } } +/// Workflow handler context. pub struct WorkflowContext<'ctx> { key: String, random_seed: u64, @@ -172,19 +190,23 @@ impl<'ctx> From<(&'ctx ContextInternal, InputMetadata)> for WorkflowContext<'ctx } impl<'ctx> WorkflowContext<'ctx> { + /// Get workflow key. pub fn key(&self) -> &str { &self.key } + /// Get request headers. pub fn headers(&self) -> &HeaderMap { &self.headers } + /// Get request headers. pub fn headers_mut(&mut self) -> &HeaderMap { &mut self.headers } } +/// Trait exposing Restate timers functionalities. pub trait ContextTimers<'ctx>: private::SealedContext<'ctx> { /// Sleep using Restate fn sleep(&self, duration: Duration) -> impl Future> + 'ctx { @@ -194,7 +216,9 @@ pub trait ContextTimers<'ctx>: private::SealedContext<'ctx> { impl<'ctx, CTX: private::SealedContext<'ctx>> ContextTimers<'ctx> for CTX {} +/// Trait exposing Restate service to service communication functionalities. pub trait ContextClient<'ctx>: private::SealedContext<'ctx> { + /// Create a [`Request`]. fn request( &self, request_target: RequestTarget, @@ -203,6 +227,31 @@ pub trait ContextClient<'ctx>: private::SealedContext<'ctx> { Request::new(self.inner_context(), request_target, req) } + /// Create a service client. The service client is generated by the [`restate_sdk_macros::service`] macro with the same name of the trait suffixed with `Client`. + /// + /// ```rust,no_run + /// # use std::time::Duration; + /// # use restate_sdk::prelude::*; + /// + /// #[restate_sdk::service] + /// trait MyService { + /// async fn handle() -> HandlerResult<()>; + /// } + /// + /// # async fn handler(ctx: Context<'_>) { + /// let client = ctx.service_client::(); + /// + /// // Do request + /// let result = client.handle().call().await; + /// + /// // Just send the request, don't wait the response + /// client.handle().send(); + /// + /// // Schedule the request to be executed later + /// client.handle().send_with_delay(Duration::from_secs(60)); + /// } + /// + /// ``` fn service_client(&self) -> C where C: IntoServiceClient<'ctx>, @@ -210,6 +259,31 @@ pub trait ContextClient<'ctx>: private::SealedContext<'ctx> { C::create_client(self.inner_context()) } + /// Create an object client. The object client is generated by the [`restate_sdk_macros::object`] macro with the same name of the trait suffixed with `Client`. + /// + /// ```rust,no_run + /// # use std::time::Duration; + /// # use restate_sdk::prelude::*; + /// + /// #[restate_sdk::object] + /// trait MyObject { + /// async fn handle() -> HandlerResult<()>; + /// } + /// + /// # async fn handler(ctx: Context<'_>) { + /// let client = ctx.object_client::("my-key"); + /// + /// // Do request + /// let result = client.handle().call().await; + /// + /// // Just send the request, don't wait the response + /// client.handle().send(); + /// + /// // Schedule the request to be executed later + /// client.handle().send_with_delay(Duration::from_secs(60)); + /// } + /// + /// ``` fn object_client(&self, key: impl Into) -> C where C: IntoObjectClient<'ctx>, @@ -217,6 +291,31 @@ pub trait ContextClient<'ctx>: private::SealedContext<'ctx> { C::create_client(self.inner_context(), key.into()) } + /// Create an workflow client. The workflow client is generated by the [`restate_sdk_macros::workflow`] macro with the same name of the trait suffixed with `Client`. + /// + /// ```rust,no_run + /// # use std::time::Duration; + /// # use restate_sdk::prelude::*; + /// + /// #[restate_sdk::workflow] + /// trait MyWorkflow { + /// async fn handle() -> HandlerResult<()>; + /// } + /// + /// # async fn handler(ctx: Context<'_>) { + /// let client = ctx.workflow_client::("my-key"); + /// + /// // Do request + /// let result = client.handle().call().await; + /// + /// // Just send the request, don't wait the response + /// client.handle().send(); + /// + /// // Schedule the request to be executed later + /// client.handle().send_with_delay(Duration::from_secs(60)); + /// } + /// + /// ``` fn workflow_client(&self, key: impl Into) -> C where C: IntoWorkflowClient<'ctx>, @@ -225,20 +324,29 @@ pub trait ContextClient<'ctx>: private::SealedContext<'ctx> { } } +/// Trait used by codegen to create the service client. pub trait IntoServiceClient<'ctx>: Sized { fn create_client(ctx: &'ctx ContextInternal) -> Self; } +/// Trait used by codegen to use the object client. pub trait IntoObjectClient<'ctx>: Sized { fn create_client(ctx: &'ctx ContextInternal, key: String) -> Self; } +/// Trait used by codegen to use the workflow client. pub trait IntoWorkflowClient<'ctx>: Sized { fn create_client(ctx: &'ctx ContextInternal, key: String) -> Self; } impl<'ctx, CTX: private::SealedContext<'ctx>> ContextClient<'ctx> for CTX {} +/// Trait exposing Restate awakeables functionalities. +/// +/// Awakeables can be used to implement external asynchronous systems interactions, +/// for example you can send a Kafka record including the awakeable id returned by [`ContextAwakeables::awakeable`], +/// and then let another service consume from Kafka the responses of given external system interaction by using +/// [`ContextAwakeables::resolve_awakeable`] or [`ContextAwakeables::reject_awakeable`]. pub trait ContextAwakeables<'ctx>: private::SealedContext<'ctx> { /// Create an awakeable fn awakeable( @@ -263,8 +371,10 @@ pub trait ContextAwakeables<'ctx>: private::SealedContext<'ctx> { impl<'ctx, CTX: private::SealedContext<'ctx>> ContextAwakeables<'ctx> for CTX {} +/// Trait exposing Restate functionalities to deal with non-deterministic operations. pub trait ContextSideEffects<'ctx>: private::SealedContext<'ctx> { - /// Run a non-deterministic operation + /// Run a non-deterministic operation and record its result. + /// fn run( &self, name: &'ctx str, @@ -278,15 +388,23 @@ pub trait ContextSideEffects<'ctx>: private::SealedContext<'ctx> { self.inner_context().run(name, run_closure) } + /// Return a random seed inherently predictable, based on the invocation id, which is not secret. + /// + /// This value is stable during the invocation lifecycle, thus across retries. fn random_seed(&self) -> u64 { private::SealedContext::random_seed(self) } + /// Return a [`rand::Rng`] instance inherently predictable, seeded with [`ContextSideEffects::random_seed`]. + /// + /// This instance is useful to generate identifiers, idempotency keys, and for uniform sampling from a set of options. + /// If a cryptographically secure value is needed, please generate that externally using [`ContextSideEffects::run`]. #[cfg(feature = "rand")] fn rand(&mut self) -> &mut rand::prelude::StdRng { private::SealedContext::rand(self) } + /// Return a random [`uuid::Uuid`], generated using [`ContextSideEffects::rand`]. #[cfg(all(feature = "rand", feature = "uuid"))] fn rand_uuid(&mut self) -> uuid::Uuid { let rand = private::SealedContext::rand(self); @@ -296,6 +414,7 @@ pub trait ContextSideEffects<'ctx>: private::SealedContext<'ctx> { impl<'ctx, CTX: private::SealedContext<'ctx>> ContextSideEffects<'ctx> for CTX {} +/// Trait exposing Restate K/V read functionalities. pub trait ContextReadState<'ctx>: private::SealedContext<'ctx> { /// Get state fn get( @@ -316,6 +435,7 @@ impl<'ctx, CTX: private::SealedContext<'ctx> + private::SealedCanReadState> Cont { } +/// Trait exposing Restate K/V write functionalities. pub trait ContextWriteState<'ctx>: private::SealedContext<'ctx> { /// Set state fn set(&self, key: &str, t: T) { @@ -338,6 +458,13 @@ impl<'ctx, CTX: private::SealedContext<'ctx> + private::SealedCanWriteState> Con { } +/// Trait exposing Restate promise functionalities. +/// +/// A promise is a durable, distributed version of a Rust oneshot channel. +/// Restate keeps track of the promises across restarts/failures. +/// +/// You can use this feature to implement interaction between different workflow handlers, e.g. to +/// send a signal from a shared handler to the workflow handler. pub trait ContextPromises<'ctx>: private::SealedContext<'ctx> { /// Create a promise fn promise( diff --git a/src/context/request.rs b/src/context/request.rs index 0b7812c..d0104f5 100644 --- a/src/context/request.rs +++ b/src/context/request.rs @@ -6,6 +6,7 @@ use std::future::Future; use std::marker::PhantomData; use std::time::Duration; +/// Target of a request to a Restate service. #[derive(Debug, Clone)] pub enum RequestTarget { Service { @@ -67,6 +68,7 @@ impl fmt::Display for RequestTarget { } } +/// This struct encapsulates the parameters for a request to a service. pub struct Request<'a, Req, Res = ()> { ctx: &'a ContextInternal, request_target: RequestTarget, @@ -84,6 +86,7 @@ impl<'a, Req, Res> Request<'a, Req, Res> { } } + /// Call a service. This returns a future encapsulating the response. pub fn call(self) -> impl Future> + Send where Req: Serialize + 'static, @@ -92,6 +95,7 @@ impl<'a, Req, Res> Request<'a, Req, Res> { self.ctx.call(self.request_target, self.req) } + /// Send the request to the service, without waiting for the response. pub fn send(self) where Req: Serialize + 'static, @@ -99,6 +103,7 @@ impl<'a, Req, Res> Request<'a, Req, Res> { self.ctx.send(self.request_target, self.req, None) } + /// Schedule the request to the service, without waiting for the response. pub fn send_with_delay(self, duration: Duration) where Req: Serialize + 'static, diff --git a/src/context/run.rs b/src/context/run.rs index 645d9c6..1d90fc4 100644 --- a/src/context/run.rs +++ b/src/context/run.rs @@ -2,7 +2,7 @@ use crate::errors::HandlerResult; use crate::serde::{Deserialize, Serialize}; use std::future::Future; -// Run +/// Run closure trait pub trait RunClosure { type Output: Deserialize + Serialize + 'static; type Fut: Future>; diff --git a/src/discovery.rs b/src/discovery.rs index a0773f8..949ad7e 100644 --- a/src/discovery.rs +++ b/src/discovery.rs @@ -1,3 +1,5 @@ +//! This module contains the generated data structures from the [service protocol manifest schema](https://github.com/restatedev/service-protocol/blob/main/endpoint_manifest_schema.json). + mod generated { #![allow(clippy::clone_on_copy)] #![allow(clippy::to_string_trait_impl)] diff --git a/src/endpoint/context.rs b/src/endpoint/context.rs index a92fcd5..29eb8f1 100644 --- a/src/endpoint/context.rs +++ b/src/endpoint/context.rs @@ -47,6 +47,9 @@ impl ContextInternalInner { } } +/// Internal context interface. +/// +/// For the high level interfaces, look at [`crate::context`]. #[derive(Clone)] pub struct ContextInternal { svc_name: String, diff --git a/src/endpoint/mod.rs b/src/endpoint/mod.rs index 160f928..3bf5a93 100644 --- a/src/endpoint/mod.rs +++ b/src/endpoint/mod.rs @@ -63,11 +63,13 @@ impl InputReceiver { } // TODO can we have the backtrace here? +/// Endpoint error. This encapsulates any error that happens within the SDK while processing a request. #[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct Error(#[from] ErrorInner); impl Error { + /// New error for unknown handler pub fn unknown_handler(service_name: &str, handler_name: &str) -> Self { Self(ErrorInner::UnknownServiceHandler( service_name.to_owned(), @@ -77,6 +79,7 @@ impl Error { } impl Error { + /// Returns the HTTP status code for this error. pub fn status_code(&self) -> u16 { match &self.0 { ErrorInner::VM(e) => e.code, @@ -167,6 +170,7 @@ impl Service for BoxedService { } } +/// Builder for [`Endpoint`] pub struct Builder { svcs: HashMap, discovery: crate::discovery::Endpoint, @@ -189,10 +193,14 @@ impl Default for Builder { } impl Builder { + /// Create a new builder for [`Endpoint`]. pub fn new() -> Self { Self::default() } + /// Add a service. + /// + /// When using the service/object/workflow macros, you need to pass the result of the `serve` method. pub fn with_service< S: Service>> + Discoverable @@ -211,11 +219,13 @@ impl Builder { self } + /// Add identity key, e.g. `publickeyv1_ChjENKeMvCtRnqG2mrBK1HmPKufgFUc98K8B3ononQvp`. pub fn with_identity_key(mut self, key: &str) -> Result { self.identity_verifier = self.identity_verifier.with_key(key)?; Ok(self) } + /// Build the [`Endpoint`]. pub fn build(self) -> Endpoint { Endpoint(Arc::new(EndpointInner { svcs: self.svcs, @@ -225,10 +235,15 @@ impl Builder { } } +/// This struct encapsulates all the business logic to handle incoming requests to the SDK, +/// including service discovery, invocations and identity verification. +/// +/// It internally wraps the provided services. This structure is cheaply cloneable. #[derive(Clone)] pub struct Endpoint(Arc); impl Endpoint { + /// Create a new builder for [`Endpoint`]. pub fn builder() -> Builder { Builder::new() } diff --git a/src/errors.rs b/src/errors.rs index e9ad1d4..276f903 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,3 +1,5 @@ +//! Error types to use within handlers. + use restate_sdk_shared_core::Failure; use std::error::Error as StdError; use std::fmt; @@ -28,6 +30,8 @@ impl StdError for HandlerErrorInner { } } +/// This error can contain either a [`TerminalError`], or any other Rust's [`StdError`]. +/// For the latter, the error is considered "retryable", and the execution will be retried. #[derive(Debug)] pub struct HandlerError(pub(crate) HandlerErrorInner); @@ -70,14 +74,19 @@ impl fmt::Display for TerminalErrorInner { impl StdError for TerminalErrorInner {} +/// Error representing the result of an operation recorded in the journal. +/// +/// When returned inside a [`crate::context::ContextSideEffects::run`] closure, or in a handler, it completes the operation with a failure value. #[derive(Debug, Clone)] pub struct TerminalError(pub(crate) TerminalErrorInner); impl TerminalError { + /// Create a new [`TerminalError`]. pub fn new(message: impl Into) -> Self { Self::new_with_code(500, message) } + /// Create a new [`TerminalError`] with a status code. pub fn new_with_code(code: u16, message: impl Into) -> Self { Self(TerminalErrorInner { code, @@ -134,4 +143,7 @@ impl From for Failure { } } +/// Result type for a Restate handler. +/// +/// All Restate handlers *MUST* use this type as return type for their handlers. pub type HandlerResult = Result; diff --git a/src/http_server.rs b/src/http_server.rs index 95a604b..b93f076 100644 --- a/src/http_server.rs +++ b/src/http_server.rs @@ -1,3 +1,5 @@ +//! Battery-packed HTTP server to expose services. + use crate::endpoint::Endpoint; use crate::hyper::HyperEndpoint; use futures::FutureExt; @@ -9,6 +11,7 @@ use std::time::Duration; use tokio::net::TcpListener; use tracing::{info, warn}; +/// Http server to expose your Restate services. pub struct HttpServer { endpoint: Endpoint, } @@ -20,20 +23,28 @@ impl From for HttpServer { } impl HttpServer { + /// Create new [`HttpServer`] from an [`Endpoint`]. pub fn new(endpoint: Endpoint) -> Self { Self { endpoint } } + /// Listen on the given address and serve. + /// + /// The future will be completed once `SIGTERM` is sent to the process. pub async fn listen_and_serve(self, addr: SocketAddr) { let listener = TcpListener::bind(addr).await.expect("listener can bind"); self.serve(listener).await; } + /// Serve on the given listener. + /// + /// The future will be completed once `SIGTERM` is sent to the process. pub async fn serve(self, listener: TcpListener) { self.serve_with_cancel(listener, tokio::signal::ctrl_c().map(|_| ())) .await; } + /// Serve on the given listener, and cancel the execution with the given future. pub async fn serve_with_cancel(self, listener: TcpListener, cancel_signal_future: impl Future) { let endpoint = HyperEndpoint::new(self.endpoint); let graceful = hyper_util::server::graceful::GracefulShutdown::new(); diff --git a/src/hyper.rs b/src/hyper.rs index 21e35df..e130ba7 100644 --- a/src/hyper.rs +++ b/src/hyper.rs @@ -1,3 +1,5 @@ +//! Hyper integration. + use crate::endpoint; use crate::endpoint::{Endpoint, InputReceiver, OutputSender}; use bytes::Bytes; @@ -17,6 +19,7 @@ use std::task::{ready, Context, Poll}; use tokio::sync::mpsc; use tracing::warn; +/// Wraps [`Endpoint`] to implement hyper [`Service`]. #[derive(Clone)] pub struct HyperEndpoint(Endpoint); diff --git a/src/lib.rs b/src/lib.rs index 4c22024..e80c540 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,41 @@ +//! # Restate Rust SDK +//! +//! [Restate](https://restate.dev/) is a system for easily building resilient applications using _distributed durable async/await_. +//! This crate is the Restate SDK for writing Restate services using Rust. +//! +//! ```rust,no_run +//! // The prelude contains all the imports you need to get started +//! use restate_sdk::prelude::*; +//! +//! // Define the service using Rust traits +//! #[restate_sdk::service] +//! trait Greeter { +//! async fn greet(name: String) -> HandlerResult; +//! } +//! +//! // Implement the service +//! struct GreeterImpl; +//! impl Greeter for GreeterImpl { +//! async fn greet(&self, _: Context<'_>, name: String) -> HandlerResult { +//! Ok(format!("Greetings {name}")) +//! } +//! } +//! +//! // Start the HTTP server to expose services +//! #[tokio::main] +//! async fn main() { +//! HttpServer::new( +//! Endpoint::builder() +//! .with_service(GreeterImpl.serve()) +//! .build(), +//! ) +//! .listen_and_serve("0.0.0.0:9080".parse().unwrap()) +//! .await; +//! } +//! ``` +//! +//! For a general overview about Restate, check out the [Restate documentation](https://docs.restate.dev). + pub mod endpoint; pub mod service; @@ -12,6 +50,7 @@ pub mod serde; pub use restate_sdk_macros::{object, service, workflow}; +/// Prelude contains all the useful imports you need to get started with Restate. pub mod prelude { #[cfg(feature = "http_server")] pub use crate::http_server::HttpServer; diff --git a/src/serde.rs b/src/serde.rs index b445aa7..103f397 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,9 +1,16 @@ +//! Serialization/Deserialization traits and concrete implementations for handlers and state. + use bytes::Bytes; use std::convert::Infallible; const APPLICATION_JSON: &str = "application/json"; const APPLICATION_OCTET_STREAM: &str = "application/octet-stream"; +/// Serialize trait for Restate services. +/// +/// Default implementations are provided for primitives, and you can use the wrapper type [`Json`] to serialize using [`serde_json`]. +/// +/// This looks similar to [`serde::Serialize`], but allows to plug-in non-serde serialization formats (e.g. like Protobuf using `prost`). pub trait Serialize { type Error: std::error::Error + Send + Sync + 'static; @@ -11,6 +18,11 @@ pub trait Serialize { } // TODO perhaps figure out how to add a lifetime here so we can deserialize to borrowed types +/// Deserialize trait for Restate services. +/// +/// Default implementations are provided for primitives, and you can use the wrapper type [`Json`] to serialize using [`serde_json`]. +/// +/// This looks similar to [`serde::Deserialize`], but allows to plug-in non-serde serialization formats (e.g. like Protobuf using `prost`). pub trait Deserialize where Self: Sized, @@ -20,6 +32,9 @@ where fn deserialize(bytes: &mut Bytes) -> Result; } +/// Trait encapsulating `content-type` information for the given serializer/deserializer. +/// +/// This is used by service discovery to correctly specify the content type. pub trait WithContentType { fn content_type() -> &'static str; } @@ -139,6 +154,7 @@ impl_serde_primitives!(f64); // --- Json responses +/// Wrapper type to use [`serde_json`] with Restate's [`Serialize`]/[`Deserialize`] traits. pub struct Json(pub T); impl Json { diff --git a/src/service.rs b/src/service.rs index b795d4b..ee7d54d 100644 --- a/src/service.rs +++ b/src/service.rs @@ -2,12 +2,19 @@ use crate::endpoint; use futures::future::BoxFuture; use std::future::Future; +/// Trait representing a Restate service. +/// +/// This is used by codegen. pub trait Service { type Future: Future> + Send + 'static; + /// Handle an incoming request. fn handle(&self, req: endpoint::ContextInternal) -> Self::Future; } +/// Trait representing a discoverable Restate service. +/// +/// This is used by codegen. pub trait Discoverable { fn discover() -> crate::discovery::Service; }