Skip to main content

Context

Struct Context 

Source
pub struct Context<'r, I, E: Render = NoRender> { /* private fields */ }
Expand description

The context a handler receives: proof that the request passed its route’s access check, the identity it carries, and the reply.

There is no public way to make one: web-net builds it only after the gate cleared the request.

use web_net::{App, Context, Guest, get};

fn home(cx: &mut Context<'_, Guest>) -> web_net::Result {
    cx.text(200, "home")
}

let app = App::builder().route(get("/", home)).build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "home");

Implementations§

Source§

impl<'r, I, E: Render> Context<'r, I, E>

Source

pub fn request(&self) -> &Request<'r>

The request.

use web_net::{App, get};

let app = App::builder()
    .route(get("/", |cx| {
        let host = cx.request().host();
        cx.text(200, host)
    }))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "localhost");
Source

pub fn method(&self) -> Method

The method.

use serve_net::Method;
use web_net::{App, Context, Guest, Route};

fn which(cx: &mut Context<'_, Guest>) -> web_net::Result {
    let m = if cx.method() == Method::Post { "post" } else { "get" };
    cx.text(200, m)
}

let app = App::builder()
    .route(Route::at("/").get(which).post(which).csrf_exempt("example"))
    .build()?;
let r = serve_net::testing::respond(&app, b"POST / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n\r\n");
assert_eq!(r.text(), "post");
Source

pub fn path(&self) -> &'r str

The canonical path inside the site.

use web_net::{App, get};

let app = App::builder()
    .base("/app")
    .route(get("/about", |cx| cx.text(200, cx.path())))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET /app/about HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "/about");
Source

pub fn header(&self, name: &str) -> Option<&'r [u8]>

The first value of a request field.

use web_net::{App, get};

let app = App::builder()
    .route(get("/", |cx| {
        let agent = cx.header("user-agent").unwrap_or(b"unknown");
        cx.bytes(200, "text/plain", agent)
    }))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\nUser-Agent: demo/1\r\n\r\n");
assert_eq!(r.text(), "demo/1");
Source

pub fn identity(&self) -> &'r I

Who the request comes from: always present, possibly a guest.

use web_net::{App, Context, Guest, Identity, get};

let app = App::builder()
    .route(get("/", |cx: &mut Context<'_, Guest>| {
        let who = if cx.identity().is_guest() { "guest" } else { "member" };
        cx.text(200, who)
    }))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "guest");
Source

pub fn id(&self) -> RequestId

The request’s reference, as shown on error pages.

use web_net::{App, get};

let app = App::builder()
    .route(get("/", |cx| {
        let reference = cx.id().to_string();
        cx.text(200, &reference)
    }))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text().len(), 16);
Source

pub fn param(&self, name: &str) -> Result<Cow<'r, str>>

A route parameter (:slug or *rest), percent-decoded.

The path is canonical (no dot segments), but a *rest value is part of a URL, not a safe file path: it can hold a drive (C:/Windows) or names a file system reads specially. Serve files with Assets, which refuses those; never join a parameter onto a folder.

use web_net::{App, get};

let app = App::builder()
    .route(get("/posts/:slug", |cx| cx.text(200, &cx.param("slug")?)))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET /posts/hello%20world HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "hello world");
§Errors

500 route.no_param when the route’s pattern has no parameter name (a mistake in the handler).

Source

pub fn param_as<T: FromStr>(&self, name: &str) -> Result<T>

A route parameter parsed as T. A value that does not parse means the page does not exist (/users/abc for a numeric id): 404.

use web_net::{App, get};

let app = App::builder()
    .route(get("/users/:id", |cx| {
        let id: u64 = cx.param_as("id")?;
        cx.text(200, &format!("user {id}"))
    }))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET /users/abc HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.status, 404);
§Errors

404 route.param when the value does not parse; as param for a name the pattern does not have.

Source

pub fn query(&self, name: &str) -> Result<Option<Cow<'r, str>>>

The first value of a query parameter, decoded (+ is a space).

use web_net::{App, get};

let app = App::builder()
    .route(get("/search", |cx| {
        let q = cx.query("q")?.unwrap_or_default();
        cx.text(200, &q)
    }))
    .build()?;
let at = |target: &str| serve_net::testing::respond(&app, format!("GET {target} HTTP/1.1\r\nHost: localhost\r\n\r\n").as_bytes());
assert_eq!(at("/search?q=red+shoes").text(), "red shoes");
assert_eq!(at("/search?q=a&q=b").status, 400);
§Errors

400 query.duplicate when the name appears twice (parameter pollution is refused, never resolved by picking one), 400 query.invalid when the value does not decode to UTF-8.

Source

pub fn body(&self) -> Result<&'r [u8]>

The raw request body. On an unsafe method its CSRF token was checked before the handler ran.

use web_net::{App, post};

let app = App::builder()
    .route(post("/echo", |cx| {
        let body = cx.body()?;
        cx.bytes(200, "text/plain", body)
    }).csrf_exempt("example: echoes the body"))
    .build()?;
let r = serve_net::testing::respond(&app, b"POST /echo HTTP/1.1\r\nHost: localhost\r\nContent-Length: 5\r\n\r\nhello");
assert_eq!(r.text(), "hello");
§Errors

None today; reading the body stays fallible so a content inspector can refuse it in a later version without changing handlers.

Source

pub fn form<T: FromForm>(&mut self) -> Result<T, FormError>

Reads the body as a typed form (urlencoded or multipart text fields), with the route’s form_limits.

On an unsafe method the CSRF token was checked before the handler ran. A form type that is posted with a token lists the token field in its ignore list (ignore = ["csrf_token"]).

The error keeps form-net’s detail, so an action can render the form again with 422; ? turns it into an Error with its status.

use web_net::form_net::{self, FromForm};

form_net::form! {
    pub struct Signup { pub email: String }
    ignore = ["csrf_token"];
}

let app = web_net::App::builder()
    .route(web_net::post("/signup", |cx| {
        let form: Signup = cx.form()?;
        cx.text(200, &form.email)
    }).csrf_exempt("example without a session"))
    .build()?;
let r = serve_net::testing::respond(&app,
    b"POST /signup HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 15\r\n\r\nemail=a%40b.com");
assert_eq!(r.text(), "a@b.com");
§Errors

form-net’s FormError: 415 for another media type, 413 for a budget passed, 400 for malformed input, 422 for fields that do not fit T, 403 for a CSRF token.

Source

pub fn form_inspected<T: FromForm, N: Inspector>( &mut self, inspector: &mut N, ) -> Result<T, FormError>

As form, with an inspector beside the parse (a content policy that sees every part and field first).

use web_net::form_net::{self, FromForm, Inspector, Refusal, Verdict};

form_net::form! {
    pub struct Signup { pub email: String }
    ignore = ["csrf_token"];
}

/// Refuses addresses at one domain.
struct NoSpam;
impl Inspector for NoSpam {
    fn field(&mut self, name: &str, value: &[u8]) -> Verdict {
        if name == "email" && value.ends_with(b"@spam.example") {
            return Verdict::Refuse(Refusal::new(422, "signup.spam"));
        }
        Verdict::Continue
    }
}

let app = web_net::App::builder()
    .route(web_net::post("/signup", |cx| {
        let form: Signup = cx.form_inspected(&mut NoSpam)?;
        cx.text(200, &form.email)
    }).csrf_exempt("example without a session"))
    .build()?;
let r = serve_net::testing::respond(&app,
    b"POST /signup HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 22\r\n\r\nemail=a%40spam.example");
assert_eq!(r.status, 422);
§Errors

As form, plus the inspector’s refusals.

Source

pub fn upload(&mut self, store: &Store) -> Result<Upload, FormError>

Available on crate feature uploads only.

Reads a multipart upload into store (feature uploads): text fields in memory, files in the store’s private temporary files, with the route’s form limits. The CSRF token was checked before the handler ran, so a forged one stores nothing. Turn the result into a typed form with T::from_upload(upload).

use web_net::form_net::Store;

let store = Store::memory_only();
let app = web_net::App::builder()
    .route(web_net::post("/upload", move |cx| {
        let upload = cx.upload(&store)?;
        let stored = upload.file("photo").is_some();
        cx.text(200, if stored { "stored" } else { "none" })
    }).csrf_exempt("example without a session"))
    .build()?;
let body = "--XyZ\r\nContent-Disposition: form-data; name=\"photo\"; filename=\"a.txt\"\r\nContent-Type: text/plain\r\n\r\nabc\r\n--XyZ--\r\n";
let req = format!("POST /upload HTTP/1.1\r\nHost: localhost\r\nContent-Type: multipart/form-data; boundary=XyZ\r\nContent-Length: {}\r\n\r\n{body}", body.len());
let r = serve_net::testing::respond(&app, req.as_bytes());
assert_eq!(r.text(), "stored");
§Errors

As form, plus the store’s errors.

Source

pub fn upload_inspected<N: Inspector>( &mut self, store: &Store, inspector: &mut N, ) -> Result<Upload, FormError>

Available on crate feature uploads only.

As upload, with an inspector beside the parse.

use web_net::form_net::{Inspector, PartHead, Refusal, Store, Verdict};

/// Takes text fields only.
struct NoFiles;
impl Inspector for NoFiles {
    fn part_start(&mut self, head: &PartHead<'_>) -> Verdict {
        if head.is_file() {
            return Verdict::Refuse(Refusal::new(422, "upload.no_files"));
        }
        Verdict::Continue
    }
}

let store = Store::memory_only();
let app = web_net::App::builder()
    .route(web_net::post("/upload", move |cx| {
        let _upload = cx.upload_inspected(&store, &mut NoFiles)?;
        cx.text(200, "ok")
    }).csrf_exempt("example without a session"))
    .build()?;
let body = "--XyZ\r\nContent-Disposition: form-data; name=\"photo\"; filename=\"a.txt\"\r\n\r\nabc\r\n--XyZ--\r\n";
let req = format!("POST /upload HTTP/1.1\r\nHost: localhost\r\nContent-Type: multipart/form-data; boundary=XyZ\r\nContent-Length: {}\r\n\r\n{body}", body.len());
let r = serve_net::testing::respond(&app, req.as_bytes());
assert_eq!(r.status, 422);
§Errors

As upload, plus the inspector’s refusals.

Source

pub fn csrf_token(&self) -> Result<String>

A CSRF token for the visitor’s session, to put in a form’s hidden csrf_token field or a script’s X-CSRF-Token header. Different on every call (masked), so a compression side channel cannot recover it.

use web_net::{App, Context, Denial, Gate, Identity, Request, csrf_sec::Csrf, get};

struct Visitor;
impl Identity for Visitor {
    fn is_guest(&self) -> bool { false }
    fn csrf_binding(&self) -> Option<&[u8]> { Some(b"the session id") }
}

// `Sessions` is a gate whose identity is `Visitor`.
let app = App::with_gate(Sessions)
    .csrf(Csrf::new(*b"a persisted 32-byte secret key!!").unwrap())
    .route(get("/form", |cx: &mut Context<'_, Visitor>| {
        let token = cx.csrf_token()?;
        cx.html(200, &format!("<input type=hidden name=csrf_token value=\"{token}\">"))
    }).public())
    .build()?;
let r = serve_net::testing::respond(&app, b"GET /form HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert!(r.text().starts_with("<input type=hidden name=csrf_token value=\""));
§Errors

500 csrf.no_engine without Builder::csrf; 500 csrf.no_binding when the identity has no binding.

Source

pub fn precondition( &self, tag: Option<&str>, modified: Option<SystemTime>, ) -> Result

Checks If-Match and If-Unmodified-Since against the current version of what an unsafe request would change (RFC 9110 section 13.2): tag is its entity tag’s opaque part, modified its modification time. Call it before changing anything.

use web_net::{App, put};

let app = App::builder()
    .route(put("/doc", |cx| {
        cx.precondition(Some("v2"), None)?; // the stored version is "v2"
        cx.text(200, "saved")
    }).csrf_exempt("example"))
    .build()?;
let send = |tag: &str| serve_net::testing::respond(&app, format!("PUT /doc HTTP/1.1\r\nHost: localhost\r\nIf-Match: \"{tag}\"\r\nContent-Length: 0\r\n\r\n").as_bytes()).status;
assert_eq!(send("v1"), 412);
assert_eq!(send("v2"), 200);
§Errors

412 precondition.failed when a condition is false. Without either field, Ok.

Source

pub fn status(&mut self, status: u16) -> &mut Self

Sets the status (default 200) for a reply written by render or reply_buffer; html, text, and bytes take theirs as an argument.

use web_net::{App, get};

let app = App::builder()
    .route(get("/", |cx| {
        cx.status(202).set_header("content-type", "text/plain");
        cx.reply_buffer().extend_from_slice(b"queued");
        Ok(())
    }))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!((r.status, r.text()), (202, "queued"));
Source

pub fn set_header(&mut self, name: &str, value: impl AsRef<[u8]>) -> &mut Self

Adds a reply field. A name that is not a token, a value with a control character, or a framing field (content-length, …) turns the reply into a 500 when the handler returns.

use web_net::{App, get};

let app = App::builder()
    .route(get("/", |cx| {
        cx.set_header("x-version", "7");
        cx.text(200, "ok")
    }))
    .route(get("/bad", |cx| {
        cx.set_header("content-length", "1"); // framing is serve-net's
        cx.text(200, "ok")
    }))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.header("x-version"), Some(&b"7"[..]));
let r = serve_net::testing::respond(&app, b"GET /bad HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.status, 500);
Source

pub fn bytes(&mut self, status: u16, content_type: &str, body: &[u8]) -> Result

Status, content_type, and the body. A content-type the handler set with set_header wins.

use web_net::{App, get};

let app = App::builder()
    .route(get("/logo.svg", |cx| cx.bytes(200, "image/svg+xml", b"<svg/>")))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET /logo.svg HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.header("content-type"), Some(&b"image/svg+xml"[..]));
§Errors

None; it returns Result so a handler can end with it.

Source

pub fn html(&mut self, status: u16, html: &str) -> Result

Status, content-type: text/html; charset=utf-8, and the HTML.

use web_net::{App, get};

let app = App::builder()
    .route(get("/", |cx| cx.html(200, "<h1>Hello</h1>")))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!((r.status, r.text()), (200, "<h1>Hello</h1>"));
§Errors

None; it returns Result so a handler can end with it.

Source

pub fn text(&mut self, status: u16, text: &str) -> Result

Status, content-type: text/plain; charset=utf-8, and the text.

use web_net::{App, get};

let app = App::builder().route(get("/", |cx| cx.text(200, "ok"))).build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "ok");
§Errors

None; it returns Result so a handler can end with it.

Source

pub fn reply_buffer(&mut self) -> &mut Vec<u8> ⓘ

The reply body’s buffer, emptied, for a writer that appends to a Vec<u8> (a template engine with its own API writing straight in). Set the status with status and content-type with set_header; the page budget does not apply here.

use std::io::Write;
use web_net::{App, get};

let app = App::builder()
    .route(get("/", |cx| {
        write!(cx.reply_buffer(), "{} + {} = {}", 1, 2, 3).map_err(|_| web_net::Error::internal("write"))?;
        cx.set_header("content-type", "text/plain");
        Ok(())
    }))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "1 + 2 = 3");
Source

pub fn accept(&self, offers: &[&'static str]) -> Option<&'static str>

The best of offers for the request’s Accept (["text/html", "application/json"]), or None when the request accepts none of them.

use web_net::{App, get};

let app = App::builder()
    .route(get("/", |cx| match cx.accept(&["text/html", "application/json"]) {
        Some("application/json") => cx.bytes(200, "application/json", b"{}"),
        _ => cx.html(200, "<p>hi</p>"),
    }))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\nAccept: application/json\r\n\r\n");
assert_eq!(r.text(), "{}");
Source

pub fn accept_language(&self, offers: &[&'static str]) -> Option<&'static str>

The best of offers for the request’s Accept-Language (["en", "fr"]), or None when the request states no preference any offer meets.

use web_net::{App, get};

let app = App::builder()
    .route(get("/", |cx| {
        let lang = cx.accept_language(&["en", "fr"]).unwrap_or("en");
        cx.text(200, lang)
    }))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\nAccept-Language: fr-CA, en;q=0.5\r\n\r\n");
assert_eq!(r.text(), "fr");

Adds a Set-Cookie field built with cookie-net (re-exported as web_net::cookie_net).

use web_net::{App, get, cookie_net::SetCookie};

let app = App::builder()
    .route(get("/", |cx| {
        cx.set_cookie(&SetCookie::new("theme", "dark").map_err(|_| web_net::Error::internal("cookie"))?);
        cx.text(200, "ok")
    }))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert!(r.header("set-cookie").is_some());
Source

pub fn not_modified(&mut self, tag: &str) -> bool

The conditional fast path for a page: sets ETag: "<tag>" and, when the request’s If-None-Match matches it (a GET or HEAD), turns the reply into 304 Not Modified and returns true, so the handler returns before building the page.

tag is the opaque part (letters, digits, and -._~ are safe), for example a revision number. An unusable tag sets nothing and returns false.

use web_net::{App, get};

let app = App::builder()
    .route(get("/", |cx| {
        if cx.not_modified("rev-7") {
            return Ok(());
        }
        cx.html(200, "<h1>page</h1>")
    }))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\nIf-None-Match: \"rev-7\"\r\n\r\n");
assert_eq!(r.status, 304);
Source

pub fn url(&self, path: &str) -> Result<String>

The site path path as a URL path: the site’s base path in front (/blog + /posts/1), so the site works installed in a folder. path is used as it is (a query and a fragment included); for a path built from a visitor’s input, use url_path, which encodes each segment.

use web_net::{App, get};

let app = App::builder()
    .base("/blog")
    .route(get("/", |cx| {
        let about = cx.url("/about")?;
        cx.html(200, &format!("<a href=\"{about}\">about</a>"))
    }))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET /blog/ HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "<a href=\"/blog/about\">about</a>");
§Errors

500 url.not_local when path is not a path of this site: it must start with one / (never // or /\, which browsers read as another host) and hold only visible ASCII.

Source

pub fn url_path(&self, segments: &[&str]) -> String

A URL path from segments, each percent-encoded as a path segment, the base path in front: ["tags", tag] gives /blog/tags/a%2Fb for the tag a/b. A segment cannot add a path level, a query, a fragment, or another host (/, ?, #, %, and spaces are encoded; characters a path segment allows, such as & and +, stay).

use web_net::{App, get};

let app = App::builder()
    .route(get("/", |cx| cx.text(200, &cx.url_path(&["tags", "c# & c++"]))))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "/tags/c%23%20&%20c++");
Source

pub fn next_path(&self) -> Result<Option<String>>

The page a sign-in redirect asked for: the next query parameter web-net puts on the redirect to the sign-in page, as a site-relative path safe to pass to redirect once the visitor signed in. None when there is none, or when it is not a path of this site (an attacker’s link to the sign-in page cannot turn it into an open redirect).

use web_net::{App, get};

let app = App::builder()
    .base("/app")
    .route(get("/login", |cx| {
        let next = cx.next_path()?.unwrap_or_else(|| "/".into());
        cx.text(200, &next)
    }))
    .build()?;
let at = |q: &str| serve_net::testing::respond(&app, format!("GET /app/login?{q} HTTP/1.1\r\nHost: localhost\r\n\r\n").as_bytes()).text().to_owned();
assert_eq!(at("next=/app/account%3Ftab%3D2"), "/account?tab=2");
assert_eq!(at("next=//evil.example"), "/");
assert_eq!(at("next=/elsewhere"), "/");
§Errors

As query: 400 when next is given twice or does not decode.

Source

pub fn redirect_external(&mut self, status: u16, url: &str) -> Result

A redirect to another site, only to a host listed with Builder::redirect_hosts: https:// (or http://), the host, and a visible-ASCII rest.

use web_net::{App, get};

let app = App::builder()
    .redirect_hosts(&["docs.example.com"])
    .route(get("/docs", |cx| cx.redirect_external(302, "https://docs.example.com/start")))
    .route(get("/away", |cx| cx.redirect_external(302, "https://elsewhere.example/")))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET /docs HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.header("location"), Some(&b"https://docs.example.com/start"[..]));
let r = serve_net::testing::respond(&app, b"GET /away HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.status, 500);
§Errors

500 redirect.not_allowed for any other target, 500 redirect.status for a status that is not a redirect.

Source

pub fn redirect(&mut self, status: u16, path: &str) -> Result

A redirect with status to a site-relative path, the site’s base path put in front. 303 answers a form that was accepted, so a reload does not post it again; 301 and 308 are permanent; 302 and 307 temporary.

use web_net::{App, post};

let app = App::builder()
    .base("/app")
    .route(post("/contact", |cx| cx.redirect(303, "/thanks")).csrf_exempt("example"))
    .build()?;
let r = serve_net::testing::respond(&app, b"POST /app/contact HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n\r\n");
assert_eq!((r.status, r.header("location")), (303, Some(&b"/app/thanks"[..])));
§Errors

500 redirect.not_local when path is not a local path (it must start with one /): redirects to other hosts are never built from a string a handler passes along (use redirect_external). 500 redirect.status for a status that is not 301, 302, 303, 307, or 308.

Source

pub fn render(&mut self, view: &E::View<'_>) -> Result

Renders the route’s template (declared with Route::template) against view into the body. The status stays as set (200 by default); content-type is HTML unless the handler set one.

use web_net::{App, Context, Guest, Output, Render, RenderError, TemplateId, get};

struct Page<'a> { title: &'a str }
struct Pages;
impl Render for Pages {
    type View<'a> = Page<'a>;
    fn template(&self, name: &str) -> Option<TemplateId> {
        (name == "page").then_some(TemplateId(0))
    }
    fn render(&self, _: TemplateId, page: &Page<'_>, out: &mut Output<'_>) -> Result<(), RenderError> {
        out.write_str(page.title) // a real engine escapes
    }
}

let app = App::builder()
    .render(Pages)
    .route(get("/", |cx: &mut Context<'_, Guest, Pages>| cx.render(&Page { title: "Home" })).template("page"))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "Home");
assert_eq!(r.header("content-type"), Some(&b"text/html; charset=utf-8"[..]));
§Errors

500 render.no_engine or render.no_template when nothing can render; the engine’s own RenderError as a 500; a checkpoint’s denial.

Source

pub fn render_named(&mut self, name: &str, view: &E::View<'_>) -> Result

Renders the template name against view. The name is resolved by the engine on every call; prefer render with a template declared on the route, resolved once at build.

use web_net::{App, Context, Guest, Output, Render, RenderError, TemplateId, get};

struct Page<'a> { title: &'a str }
struct Pages;
impl Render for Pages {
    type View<'a> = Page<'a>;
    fn template(&self, name: &str) -> Option<TemplateId> {
        (name == "page").then_some(TemplateId(0))
    }
    fn render(&self, _: TemplateId, page: &Page<'_>, out: &mut Output<'_>) -> Result<(), RenderError> {
        out.write_str(page.title) // a real engine escapes
    }
}

let app = App::builder()
    .render(Pages)
    .route(get("/", |cx: &mut Context<'_, Guest, Pages>| cx.render_named("page", &Page { title: "Named" })))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "Named");
§Errors

As render.

Source§

impl<I, E> Context<'_, I, E>
where E: for<'a> Render<View<'a> = dyn View + 'a>,

Source

pub fn render_view(&mut self, view: &dyn View) -> Result

render for an engine that reads any View (a dynamic engine): takes &dyn View, so a Fields or any view of yours goes in as it is.

use web_net::{App, Fields, Output, Render, RenderError, TemplateId, Value, View, get};

/// Writes the view's `title` field.
struct Echo;
impl Render for Echo {
    type View<'a> = dyn View + 'a;
    fn template(&self, _: &str) -> Option<TemplateId> { Some(TemplateId(0)) }
    fn render(&self, _: TemplateId, view: &dyn View, out: &mut Output<'_>) -> Result<(), RenderError> {
        let key = view.shape().key("title").ok_or(RenderError::new("echo.no_title"))?;
        out.write_str(view.get(key).as_text().unwrap_or("")) // a real engine escapes
    }
}

let app = App::builder()
    .render(Echo)
    .route(get("/", |cx| cx.render_view(&Fields(&[("title", Value::Text("Home"))]))).template("page"))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "Home");
§Errors

As render.

Source

pub fn render_view_named(&mut self, name: &str, view: &dyn View) -> Result

render_named for an engine that reads any View.

use web_net::{App, Fields, Output, Render, RenderError, TemplateId, Value, View, get};

/// Writes the view's `title` field.
struct Echo;
impl Render for Echo {
    type View<'a> = dyn View + 'a;
    fn template(&self, _: &str) -> Option<TemplateId> { Some(TemplateId(0)) }
    fn render(&self, _: TemplateId, view: &dyn View, out: &mut Output<'_>) -> Result<(), RenderError> {
        let key = view.shape().key("title").ok_or(RenderError::new("echo.no_title"))?;
        out.write_str(view.get(key).as_text().unwrap_or("")) // a real engine escapes
    }
}

let app = App::builder()
    .render(Echo)
    .route(get("/", |cx| cx.render_view_named("page", &Fields(&[("title", Value::Text("Named"))]))))
    .build()?;
let r = serve_net::testing::respond(&app, b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assert_eq!(r.text(), "Named");
§Errors

As render.

Trait Implementations§

Source§

impl<I, E: Render> Debug for Context<'_, I, E>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<'r, I, E = NoRender> !RefUnwindSafe for Context<'r, I, E>

§

impl<'r, I, E = NoRender> !UnwindSafe for Context<'r, I, E>

§

impl<'r, I, E> Forget for Context<'r, I, E>

§

impl<'r, I, E> Freeze for Context<'r, I, E>
where &'r I: Freeze, &'r Shared<E>: Freeze, &'r [Arc<dyn Fn(&mut Context<'_, I, E>) -> Result<(), Error> + Sync + Send>]: Freeze,

§

impl<'r, I, E> Move for Context<'r, I, E>

§

impl<'r, I, E> Send for Context<'r, I, E>
where &'r I: Send, &'r Shared<E>: Send, &'r [Arc<dyn Fn(&mut Context<'_, I, E>) -> Result<(), Error> + Sync + Send>]: Send,

§

impl<'r, I, E> Sync for Context<'r, I, E>
where &'r I: Sync, &'r Shared<E>: Sync, &'r [Arc<dyn Fn(&mut Context<'_, I, E>) -> Result<(), Error> + Sync + Send>]: Sync,

§

impl<'r, I, E> Unpin for Context<'r, I, E>
where &'r I: Unpin, &'r Shared<E>: Unpin, &'r [Arc<dyn Fn(&mut Context<'_, I, E>) -> Result<(), Error> + Sync + Send>]: Unpin,

§

impl<'r, I, E> UnsafeUnpin for Context<'r, I, E>
where &'r I: UnsafeUnpin, &'r Shared<E>: UnsafeUnpin, &'r [Arc<dyn Fn(&mut Context<'_, I, E>) -> Result<(), Error> + Sync + Send>]: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.