Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ name = "tracing"
path = "examples/tracing.rs"
required-features = ["tracing-span-filter"]

[[example]]
name = "schema"
path = "examples/schema.rs"
required-features = ["schemars"]

[features]
default = ["http_server", "rand", "uuid", "tracing-span-filter"]
hyper = ["dep:hyper", "http-body-util", "restate-sdk-shared-core/http"]
Expand All @@ -30,6 +35,7 @@ rand = { version = "0.9", optional = true }
regress = "0.10"
restate-sdk-macros = { version = "0.4", path = "macros" }
restate-sdk-shared-core = { version = "0.3.0", features = ["request_identity", "sha2_random_seed", "http"] }
schemars = { version = "1.0.0-alpha.17", optional = true }
serde = "1.0"
serde_json = "1.0"
thiserror = "2.0"
Expand All @@ -44,6 +50,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter", "registry"] }
trybuild = "1.0"
reqwest = { version = "0.12", features = ["json"] }
rand = "0.9"
schemars = "1.0.0-alpha.17"

[build-dependencies]
jsonptr = "0.5.1"
Expand Down
65 changes: 65 additions & 0 deletions examples/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Run with auto-generated schemas for `Json<Product>` using `schemars`:
//! cargo run --example schema --features schemars
//!
//! Run with primitive schemas only:
//! cargo run --example schema

use restate_sdk::prelude::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::time::Duration;

#[derive(Serialize, Deserialize, JsonSchema)]
struct Product {
id: String,
name: String,
price_cents: u32,
}

#[restate_sdk::service]
trait CatalogService {
async fn get_product_by_id(product_id: String) -> Result<Json<Product>, HandlerError>;
async fn save_product(product: Json<Product>) -> Result<String, HandlerError>;
async fn is_in_stock(product_id: String) -> Result<bool, HandlerError>;
}

struct CatalogServiceImpl;

impl CatalogService for CatalogServiceImpl {
async fn get_product_by_id(
&self,
ctx: Context<'_>,
product_id: String,
) -> Result<Json<Product>, HandlerError> {
ctx.sleep(Duration::from_millis(50)).await?;
Ok(Json(Product {
id: product_id,
name: "Sample Product".to_string(),
price_cents: 1995,
}))
}

async fn save_product(
&self,
_ctx: Context<'_>,
product: Json<Product>,
) -> Result<String, HandlerError> {
Ok(product.0.id)
}

async fn is_in_stock(
&self,
_ctx: Context<'_>,
product_id: String,
) -> Result<bool, HandlerError> {
Ok(!product_id.contains("out-of-stock"))
}
}

#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
HttpServer::new(Endpoint::builder().bind(CatalogServiceImpl.serve()).build())
.listen_and_serve("0.0.0.0:9080".parse().unwrap())
.await;
}
25 changes: 23 additions & 2 deletions macros/src/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,32 @@ impl<'a> ServiceGenerator<'a> {
quote! { None }
};

let input_schema = match &handler.arg {
Some(PatType { ty, .. }) => {
quote! {
Some(::restate_sdk::discovery::InputPayload::from_metadata::<#ty>())
}
}
None => quote! {
Some(::restate_sdk::discovery::InputPayload::empty())
}
};

let output_ty = &handler.output_ok;
let output_schema = match output_ty {
syn::Type::Tuple(tuple) if tuple.elems.is_empty() => quote! {
Some(::restate_sdk::discovery::OutputPayload::empty())
},
_ => quote! {
Some(::restate_sdk::discovery::OutputPayload::from_metadata::<#output_ty>())
}
};

quote! {
::restate_sdk::discovery::Handler {
name: ::restate_sdk::discovery::HandlerName::try_from(#handler_literal).expect("Handler name valid"),
input: None,
output: None,
input: #input_schema,
output: #output_schema,
ty: #handler_ty,
}
}
Expand Down
40 changes: 40 additions & 0 deletions src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,43 @@ mod generated {
}

pub use generated::*;

use crate::serde::PayloadMetadata;

impl InputPayload {
pub fn empty() -> Self {
Self {
content_type: None,
json_schema: None,
required: None,
}
}

pub fn from_metadata<T: PayloadMetadata>() -> Self {
let input_metadata = T::input_metadata();
Self {
content_type: Some(input_metadata.accept_content_type.to_owned()),
json_schema: T::json_schema(),
required: Some(input_metadata.is_required),
}
}
}

impl OutputPayload {
pub fn empty() -> Self {
Self {
content_type: None,
json_schema: None,
set_content_type_if_empty: Some(false),
}
}

pub fn from_metadata<T: PayloadMetadata>() -> Self {
let output_metadata = T::output_metadata();
Self {
content_type: Some(output_metadata.content_type.to_owned()),
json_schema: T::json_schema(),
set_content_type_if_empty: Some(output_metadata.set_content_type_if_empty),
}
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
//! - [Scheduling & Timers][crate::context::ContextTimers]: Let a handler pause for a certain amount of time. Restate durably tracks the timer across failures.
//! - [Awakeables][crate::context::ContextAwakeables]: Durable Futures to wait for events and the completion of external tasks.
//! - [Error Handling][crate::errors]: Restate retries failures infinitely. Use `TerminalError` to stop retries.
//! - [Serialization][crate::serde]: The SDK serializes results to send them to the Server.
//! - [Serialization][crate::serde]: The SDK serializes results to send them to the Server. Includes [Schema Generation and payload metadata](crate::serde::PayloadMetadata) for documentation & discovery.
//! - [Serving][crate::http_server]: Start an HTTP server to expose services.
//!
//! # SDK Overview
Expand Down
Loading