Attribute Macro server

Source
#[server]
Expand description

Declares that a function is a server function. This means that its body will only run on the server, i.e., when the ssr feature is enabled on this crate.

§Usage

#[server]
pub async fn blog_posts(
    category: String,
) -> ServerFnResult<Vec<BlogPost>> {
    let posts = load_posts(&category).await?;
    // maybe do some other work
    Ok(posts)
}

§Named Arguments

You can use any combination of the following named arguments:

  • name: sets the identifier for the server function’s type, which is a struct created to hold the arguments (defaults to the function identifier in PascalCase). Example: name = MyServerFunction.
  • prefix: a prefix at which the server function handler will be mounted (defaults to /api). Example: prefix = "/my_api".
  • endpoint: specifies the exact path at which the server function handler will be mounted, relative to the prefix (defaults to the function name followed by unique hash). Example: endpoint = "my_fn".
  • input: the encoding for the arguments (defaults to PostUrl).
    • The input argument specifies how the function arguments are encoded for transmission.
    • Acceptable values include:
      • PostUrl: A POST request with URL-encoded arguments, suitable for form-like submissions.
      • Json: A POST request where the arguments are encoded as JSON. This is a common choice for modern APIs.
      • Cbor: A POST request with CBOR-encoded arguments, useful for binary data transmission with compact encoding.
      • GetUrl: A GET request with URL-encoded arguments, suitable for simple queries or when data fits in the URL.
      • GetCbor: A GET request with CBOR-encoded arguments, useful for query-style APIs when the payload is binary.
  • output: the encoding for the response (defaults to Json).
    • The output argument specifies how the server should encode the response data.
    • Acceptable values include:
      • Json: A response encoded as JSON (default). This is ideal for most web applications.
      • Cbor: A response encoded in the CBOR format for efficient, binary-encoded data.
  • client: a custom Client implementation that will be used for this server function. This allows customization of the client-side behavior if needed.
  • encoding: (legacy, may be deprecated in future) specifies the encoding, which may be one of the following (not case sensitive):
    • "Url": POST request with URL-encoded arguments and JSON response
    • "GetUrl": GET request with URL-encoded arguments and JSON response
    • "Cbor": POST request with CBOR-encoded arguments and response
    • "GetCbor": GET request with URL-encoded arguments and CBOR response
  • req and res: specify the HTTP request and response types to be used on the server. These are typically necessary if you are integrating with a custom server framework (other than Actix/Axum). Example: req = SomeRequestType, res = SomeResponseType.

§Advanced Usage of input and output Fields

The input and output fields allow you to customize how arguments and responses are encoded and decoded. These fields impose specific trait bounds on the types you use. Here are detailed examples for different scenarios:

§output = StreamingJson

Setting the output type to StreamingJson requires the return type to implement From<JsonStream<T>>, where T implements serde::Serialize and serde::de::DeserializeOwned.

#[server(output = StreamingJson)]
pub async fn json_stream_fn() -> Result<JsonStream<String>, ServerFnError> {
    todo!()
}

§output = StreamingText

Setting the output type to StreamingText requires the return type to implement From<TextStream>.

#[server(output = StreamingText)]
pub async fn text_stream_fn() -> Result<TextStream, ServerFnError> {
    todo!()
}

§output = PostUrl

Setting the output type to PostUrl requires the return type to implement Serialize and Deserialize. Note that this uses serde_qs, which imposes the following constraints:

  • The structure must be less than 5 levels deep.
  • The structure must not contain any serde(flatten) attributes.
#[server(output = PostUrl)]
pub async fn form_fn() -> Result<TextStream, ServerFnError> {
    todo!()
}

These examples illustrate how the output type impacts the bounds and expectations for your server function. Ensure your return types comply with these requirements.

#[server(
  name = SomeStructName,
  prefix = "/my_api",
  endpoint = "my_fn",
  input = Cbor,
  output = Json
)]
pub async fn my_wacky_server_fn(input: Vec<String>) -> ServerFnResult<usize> {
  unimplemented!()
}

// expands to
#[derive(Deserialize, Serialize)]
struct SomeStructName {
  input: Vec<String>
}

impl ServerFn for SomeStructName {
  const PATH: &'static str = "/my_api/my_fn";

  // etc.
}

§Adding layers to server functions

Layers allow you to transform the request and response of a server function. You can use layers to add authentication, logging, or other functionality to your server functions. Server functions integrate with the tower ecosystem, so you can use any layer that is compatible with tower.

Common layers include:

You can add a tower Layer to your server function with the middleware attribute:

#[server]
// The TraceLayer will log all requests to the console
#[middleware(tower_http::timeout::TimeoutLayer::new(std::time::Duration::from_secs(5)))]
pub async fn my_wacky_server_fn(input: Vec<String>) -> ServerFnResult<usize> {
    unimplemented!()
}

§Extracting additional data from requests

Server functions automatically handle serialization and deserialization of arguments and responses. However, you may want to extract additional data from the request, such as the user’s session or authentication information. You can do this with the extract function. This function returns any type that implements the FromRequestParts trait:

#[server]
pub async fn my_wacky_server_fn(input: Vec<String>) -> ServerFnResult<String> {
    let headers: axum::http::header::HeaderMap = extract().await?;
    Ok(format!("The server got a request with headers: {:?}", headers))
}

§Sharing data with server functions

You may need to share context with your server functions like a database pool. Server functions can access any context provided through the launch builder. You can access this context with the FromContext extractor:

#[derive(Clone, Copy, Debug)]
struct DatabasePool;

fn main() {
    LaunchBuilder::new()
        .with_context(server_only! {
            DatabasePool
        })
        .launch(app);
}

#[server]
pub async fn my_wacky_server_fn(input: Vec<String>) -> ServerFnResult<String> {
    let FromContext(pool): FromContext<DatabasePool> = extract().await?;
    Ok(format!("The server read {:?} from the shared context", pool))
}