From 89db9ba8d0002e6d8252aa3348dc77c621fed45b Mon Sep 17 00:00:00 2001 From: Gordon Farquharson Date: Tue, 14 Apr 2026 09:01:09 +0100 Subject: [PATCH 1/2] update docs --- docs/CDN_APPS.md | 98 +++++++++++++++----- docs/INDEX.md | 12 +-- docs/quickstart.md | 8 +- fastedge-plugin-source/.generation-config.md | 62 +++++++++++-- 4 files changed, 136 insertions(+), 44 deletions(-) diff --git a/docs/CDN_APPS.md b/docs/CDN_APPS.md index 12bbb6a..70f166f 100644 --- a/docs/CDN_APPS.md +++ b/docs/CDN_APPS.md @@ -89,7 +89,7 @@ proxy_wasm::main! {{ ### Root Context -The root context is a singleton created once when the filter loads. Its primary role is to create a new HTTP context for each incoming request. +The root context is a singleton created once when the filter loads. Its primary role is to create a new HTTP context for each lifecycle callback invocation. ```rust,no_run # use proxy_wasm::traits::*; @@ -109,11 +109,11 @@ impl RootContext for MyAppRoot { } ``` -`get_type()` must return `Some(ContextType::HttpContext)` for HTTP traffic interception. `create_http_context` is called once per request and receives a unique `context_id`. +`get_type()` must return `Some(ContextType::HttpContext)` for HTTP traffic interception. `create_http_context` is called once per lifecycle callback invocation and receives a unique `context_id`. ### HTTP Context -The HTTP context is where request and response processing happens. A new instance is created for each request by `create_http_context`. +The HTTP context is where request and response processing happens. A new instance is created for each lifecycle callback invocation — not once per request. See [Hook State Isolation](#hook-state-isolation) for the consequences this has on state management. ```rust,no_run # use proxy_wasm::traits::*; @@ -175,6 +175,40 @@ impl HttpContext for MyApp { } ``` +### Hook State Isolation + +On the FastEdge CDN platform, an HTTP context instance exists only for the duration of a single lifecycle callback invocation. It does **not** persist across the request. Different hooks may run on entirely different servers: `on_http_request_headers` runs in nginx, while `on_http_request_body`, `on_http_response_headers`, and `on_http_response_body` run in core-proxy. + +This has critical consequences for application design: + +- Struct fields on the HTTP context do **not** persist between callbacks. +- A fresh context instance is created for each callback invocation. +- Storing data as a struct field in one callback and reading it in another callback does **not** work. + +To pass data between callbacks, use `self.set_property` and `self.get_property` with a custom property path. The host preserves these values across callback invocations for the same logical request: + +```rust,no_run +# use proxy_wasm::traits::*; +# use proxy_wasm::types::*; +# struct MyApp; +# impl Context for MyApp {} +impl HttpContext for MyApp { + fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action { + // Store a value for use in a later callback + self.set_property(vec!["my_custom_key"], Some(b"my_value")); + Action::Continue + } + + fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action { + // Retrieve the value set in a previous callback + if let Some(value) = self.get_property(vec!["my_custom_key"]) { + let _ = value; // use value + } + Action::Continue + } +} +``` + ## Request and Response Manipulation ### Reading Headers and Properties @@ -188,15 +222,13 @@ impl HttpContext for MyApp { fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action { // Read a request header if let Some(auth) = self.get_http_request_header("Authorization") { - // use auth value - let _ = auth; + let _ = auth; // use auth value } // Read a request property (UTF-8 string) if let Some(path_bytes) = self.get_property(vec!["request.path"]) { if let Ok(path) = std::str::from_utf8(&path_bytes) { - // use path - let _ = path; + let _ = path; // use path } } @@ -226,13 +258,15 @@ impl HttpContext for MyApp { fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action { // Add a new response header self.add_http_response_header("x-powered-by", "FastEdge"); - // Remove a response header + // Attempt to remove a response header self.set_http_response_header("server", None); Action::Continue } } ``` +**Known limitation**: On the FastEdge CDN platform, passing `None` to `set_http_request_header` or `set_http_response_header` sets the header value to an empty string rather than removing the header entirely. When checking for header absence, test for an empty string as well as a missing value. + ### Generating Responses To short-circuit the request and respond directly to the client without forwarding to origin, call `send_http_response` and return `Action::Pause`. @@ -264,14 +298,28 @@ impl HttpContext for MyApp { CDN apps access request metadata through `self.get_property(vec![...])`. The return type is `Option>`. -| Property path | Type | Description | -| -------------------------- | --------------------- | ------------------------------------------- | -| `["request.path"]` | UTF-8 string | Request URL path | -| `["request.query"]` | UTF-8 string | Query string | -| `["request.country"]` | UTF-8 string | Client country code (geo-IP lookup) | -| `["response", "status"]` | 2-byte big-endian u16 | Response status code (response phase only) | - -Most properties are UTF-8 strings that can be decoded with `std::str::from_utf8()`. The `response.status` property is a binary-encoded integer, not a string — it must be decoded as a big-endian `u16`: +| Property | Encoding | Description | +| ---------------------- | --------------------- | ------------------------------------------------------ | +| `request.path` | UTF-8 string | URL path | +| `request.query` | UTF-8 string | Query string | +| `request.url` | UTF-8 string | Full request URL | +| `request.host` | UTF-8 string | Domain (may have `shield_` prefix on edge shield nodes) | +| `request.scheme` | UTF-8 string | HTTP scheme (from X-Forwarded-Proto) | +| `request.extension` | UTF-8 string | File extension | +| `request.x_real_ip` | UTF-8 string | Client IP address | +| `request.country` | UTF-8 string | 2-letter ISO country code (geo-IP lookup) | +| `request.country.name` | UTF-8 string | Full country name | +| `request.city` | UTF-8 string | City name | +| `request.region` | UTF-8 string | Region/state | +| `request.continent` | UTF-8 string | Continent | +| `request.asn` | UTF-8 string | Autonomous System Number | +| `request.geo.lat` | UTF-8 string | Latitude | +| `request.geo.long` | UTF-8 string | Longitude | +| `response.status` | 2-byte big-endian u16 | Response status code (response phase only) | + +Most properties are UTF-8 strings decoded with `std::str::from_utf8()`. The `response.status` property is binary-encoded and must be decoded as a big-endian `u16`. Do not use `String::from_utf8` for this property. + +Geo-IP properties (`request.country`, `request.country.name`, `request.city`, `request.region`, `request.continent`, `request.geo.lat`, `request.geo.long`) are derived from the client IP address. ```rust,no_run # use proxy_wasm::traits::*; @@ -327,15 +375,15 @@ Provides persistent key-value storage. The API shape mirrors `fastedge::key_valu pub struct Store { /* ... */ } ``` -| Method | Return Type | Description | -| ------------------------------------------------------- | ------------------------------------ | ------------------------------------------------------- | -| `Store::new()` | `Result` | Open the default store | -| `Store::open(name: &str)` | `Result` | Open a named store | -| `Store::get(key: &str)` | `Result>, Error>` | Get the value for a key; `None` if key does not exist | -| `Store::scan(pattern: &str)` | `Result, Error>` | List keys matching a glob-style pattern | -| `Store::zrange_by_score(key: &str, min: f64, max: f64)` | `Result, f64)>, Error>` | Get sorted-set members with scores between min and max | -| `Store::zscan(key: &str, pattern: &str)` | `Result, f64)>, Error>` | Scan sorted-set members matching a pattern | -| `Store::bf_exists(key: &str, item: &str)` | `Result` | Test whether an item is in a Bloom filter | +| Method | Return Type | Description | +| --------------------------------------------------------- | ------------------------------------ | ------------------------------------------------------ | +| `Store::new()` | `Result` | Open the default store | +| `Store::open(name: &str)` | `Result` | Open a named store | +| `Store::get(key: &str)` | `Result>, Error>` | Get the value for a key; `None` if key does not exist | +| `Store::scan(pattern: &str)` | `Result, Error>` | List keys matching a glob-style pattern | +| `Store::zrange_by_score(key: &str, min: f64, max: f64)` | `Result, f64)>, Error>` | Get sorted-set members with scores between min and max | +| `Store::zscan(key: &str, pattern: &str)` | `Result, f64)>, Error>` | Scan sorted-set members matching a pattern | +| `Store::bf_exists(key: &str, item: &str)` | `Result` | Test whether an item is in a Bloom filter | #### `Error` diff --git a/docs/INDEX.md b/docs/INDEX.md index 3c39e02..0e88e9f 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -4,12 +4,12 @@ Documentation for the `fastedge` crate (v0.3.5) — a Rust SDK for building edge ## Documents -| File | Description | -| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [quickstart.md](quickstart.md) | Getting started: project setup, writing a handler, building to WASM (`wasm32-wasip2` for async, `wasm32-wasip1` for basic/CDN) | -| [SDK_API.md](SDK_API.md) | Core API: handler macros (`#[wstd::http_server]`, `#[fastedge::http]`), Body type, outbound HTTP (`send_request`), errors, feature flags | -| [HOST_SERVICES.md](HOST_SERVICES.md) | Host services for HTTP apps: key-value store, secrets, dictionary, diagnostics | -| [CDN_APPS.md](CDN_APPS.md) | CDN apps: proxy-wasm lifecycle, `fastedge::proxywasm::*` API surface, request/response manipulation | +| File | Description | +| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| [quickstart.md](quickstart.md) | Getting started: project setup, writing a handler, building to WASM (`wasm32-wasip2` for async, `wasm32-wasip1` for basic/CDN) | +| [SDK_API.md](SDK_API.md) | Core API: handler macros (`#[wstd::http_server]`, `#[fastedge::http]`), Body type, outbound HTTP (`send_request`), errors, feature flags | +| [HOST_SERVICES.md](HOST_SERVICES.md) | Host services for HTTP apps: key-value store, secrets, dictionary, diagnostics | +| [CDN_APPS.md](CDN_APPS.md) | CDN apps: proxy-wasm lifecycle, `fastedge::proxywasm::*` API surface, request/response manipulation | ## Suggested Reading Order diff --git a/docs/quickstart.md b/docs/quickstart.md index e6d21bf..418dda5 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -133,10 +133,10 @@ Both commands produce a `.wasm` binary in the respective `target//releas ## Feature Flags -| Feature | Default | Description | -| ------------- | ------- | ----------------------------------------- | -| `proxywasm` | yes | Enable ProxyWasm compatibility layer | -| `json` | no | Enable JSON body support via `serde_json` | +| Feature | Default | Description | +| ----------- | ------- | ----------------------------------------- | +| `proxywasm` | yes | Enable ProxyWasm compatibility layer | +| `json` | no | Enable JSON body support via `serde_json` | Enable the `json` feature in `Cargo.toml`: diff --git a/fastedge-plugin-source/.generation-config.md b/fastedge-plugin-source/.generation-config.md index a18e899..7d0edb2 100644 --- a/fastedge-plugin-source/.generation-config.md +++ b/fastedge-plugin-source/.generation-config.md @@ -346,9 +346,9 @@ CRITICAL: The `proxywasm` feature flag is REQUIRED for CDN apps to access `faste The lifecycle is the most important concept for CDN app developers. Document each phase: 1. **Entry point** — `proxy_wasm::main!` macro. Sets log level and registers the root context factory. -2. **Root Context** — Implements `proxy_wasm::traits::RootContext`. Creates HTTP contexts per-request. Must implement: +2. **Root Context** — Implements `proxy_wasm::traits::RootContext`. Creates HTTP contexts for each hook invocation. Must implement: - `get_type()` → returns `Some(ContextType::HttpContext)` - - `create_http_context(context_id)` → returns a new per-request context + - `create_http_context(context_id)` → returns a new HTTP context instance 3. **HTTP Context** — Implements `proxy_wasm::traits::HttpContext`. This is where request/response processing happens. Key lifecycle callbacks: - `on_http_request_headers(num_headers, end_of_stream)` → inspect/modify request headers. Return `Action::Continue` to pass through, `Action::Pause` to stop. - `on_http_request_body(body_size, end_of_stream)` → inspect/modify request body. Use `Action::StopIterationAndBuffer` to accumulate body chunks. @@ -356,6 +356,31 @@ The lifecycle is the most important concept for CDN app developers. Document eac - `on_http_response_body(body_size, end_of_stream)` → inspect/modify response body from origin. 4. **Context trait** — All contexts must also implement `proxy_wasm::traits::Context` (can be empty impl). +**CRITICAL — Hook State Isolation:** + +On the FastEdge CDN platform, **an HTTP Context instance only exists for the duration of a single lifecycle callback invocation**. It does NOT persist across the request. Different hooks may run on entirely different servers — `on_http_request_headers` runs in nginx, while `on_http_request_body`, `on_http_response_headers`, `on_http_response_body` run in core-proxy. + +This means: +- Struct fields on the HTTP Context do NOT persist between callbacks +- A fresh context is created for each callback invocation +- `create_http_context` is called once per callback, not once per request + +To pass data between callbacks (e.g., from `on_http_request_headers` to `on_http_response_headers`), use `self.set_property` / `self.get_property`: +```rust +// In on_http_request_headers: +self.set_property(vec!["my_custom_key"], Some(b"my_value")); + +// In on_http_response_headers: +if let Some(value) = self.get_property(vec!["my_custom_key"]) { + // use value +} +``` + +Do NOT document or suggest: +- "A new instance is created for each request" — WRONG, it is created per callback invocation +- Storing state as struct fields and reading them in later callbacks — WRONG, fields do not persist +- Any pattern that implies the same Context object is reused across callbacks + **Action return values — document what each means:** - `Action::Continue` — pass the request/response through to the next stage @@ -370,14 +395,32 @@ The lifecycle is the most important concept for CDN app developers. Document eac - `self.get_http_request_header(name)` — read a request header - `self.get_property(path)` — read request properties (see Request Properties section) -**Request Properties:** -CDN apps access request metadata via `self.get_property(vec![...])`. Document available properties: +**Known limitation — header removal:** On the FastEdge CDN (nginx-based), removing a header via the proxy-wasm API sets its value to an empty string rather than fully removing it. Keep this in mind when checking for header presence. -- `["request.path"]` — request URL path (UTF-8 string) -- `["request.query"]` — query string (UTF-8 string) -- `["request.country"]` — client country code (UTF-8 string, geo-IP) -- `["response", "status"]` — response status code (response phase only). CRITICAL: this is a **2-byte big-endian u16**, NOT a UTF-8 string. Decode with `u16::from_be_bytes([bytes[0], bytes[1]])` after checking `bytes.len() == 2`. Do NOT use `String::from_utf8`. -- Properties return `Option>` — most are UTF-8 strings that can be decoded with `std::str::from_utf8()`, but `response.status` is binary (see above). Always document the encoding for each property. +**Request Properties:** +CDN apps access request metadata via `self.get_property(vec![...])`. Document ALL available properties on the FastEdge platform: + +| Property | Encoding | Description | +| ---------------------- | --------------------- | ------------------------------------------------------------------------------- | +| `request.path` | UTF-8 string | URL path | +| `request.query` | UTF-8 string | Query string | +| `request.url` | UTF-8 string | Full request URL | +| `request.host` | UTF-8 string | Domain (may have `shield_` prefix on edge shield nodes) | +| `request.scheme` | UTF-8 string | HTTP scheme (from X-Forwarded-Proto) | +| `request.extension` | UTF-8 string | File extension | +| `request.x_real_ip` | UTF-8 string | Client IP address | +| `request.country` | UTF-8 string | 2-letter ISO country code (geo-IP) | +| `request.country.name` | UTF-8 string | Full country name | +| `request.city` | UTF-8 string | City name | +| `request.region` | UTF-8 string | Region/state | +| `request.continent` | UTF-8 string | Continent | +| `request.asn` | UTF-8 string | Autonomous System Number | +| `request.geo.lat` | UTF-8 string | Latitude | +| `request.geo.long` | UTF-8 string | Longitude | +| `response.status` | 2-byte big-endian u16 | Response status code (**binary, NOT a string** — decode with `u16::from_be_bytes`) | + +- Properties return `Option>` — most are UTF-8 strings decoded with `std::str::from_utf8()`, but `response.status` is binary. CRITICAL: Decode `response.status` with `u16::from_be_bytes([bytes[0], bytes[1]])` after checking `bytes.len() == 2`. Do NOT use `String::from_utf8`. +- Geo properties are lazily computed from the client IP address. **ProxyWasm Host Services (`fastedge::proxywasm::*`):** @@ -459,6 +502,7 @@ Include a clear comparison showing developers which import paths to use: ### HTTP Context ### Lifecycle Callbacks ### Action Return Values +### Hook State Isolation ## Request and Response Manipulation ### Reading Headers and Properties ### Modifying Headers From 237d074f6d8ddc0751ebd97030bc180351a5f1d0 Mon Sep 17 00:00:00 2001 From: Gordon Farquharson Date: Tue, 14 Apr 2026 10:02:26 +0100 Subject: [PATCH 2/2] docs update --- context/reference/PROPERTIES_REFERENCE.md | 6 +- docs/CDN_APPS.md | 78 ++++++++++---------- docs/HOST_SERVICES.md | 26 +++---- docs/INDEX.md | 12 +-- docs/SDK_API.md | 8 +- fastedge-plugin-source/.generation-config.md | 2 +- 6 files changed, 68 insertions(+), 64 deletions(-) diff --git a/context/reference/PROPERTIES_REFERENCE.md b/context/reference/PROPERTIES_REFERENCE.md index e3cf126..a2d8ae2 100644 --- a/context/reference/PROPERTIES_REFERENCE.md +++ b/context/reference/PROPERTIES_REFERENCE.md @@ -2,6 +2,8 @@ Properties are read-only metadata about the current request and client, available via `proxy_get_property()` in ProxyWasm apps. They provide context that isn't in the HTTP headers themselves. +**Path format:** Always pass the property identifier as a single dotted string in a one-element vec — e.g., `vec!["request.path"]`, `vec!["request.geo.long"]`. Do **not** split on dots (e.g., `vec!["request", "country"]` is incorrect). + --- ## Available Properties @@ -40,13 +42,13 @@ Properties are read-only metadata about the current request and client, availabl // In an HttpContext implementation fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action { // Get client's country - if let Some(country) = self.get_property(vec!["request", "country"]) { + if let Some(country) = self.get_property(vec!["request.country"]) { let country_str = String::from_utf8(country).unwrap_or_default(); // Use for geo-routing, access control, etc. } // Get client IP - if let Some(ip) = self.get_property(vec!["request", "x_real_ip"]) { + if let Some(ip) = self.get_property(vec!["request.x_real_ip"]) { // Use for rate limiting, logging, etc. } diff --git a/docs/CDN_APPS.md b/docs/CDN_APPS.md index 70f166f..13c18bd 100644 --- a/docs/CDN_APPS.md +++ b/docs/CDN_APPS.md @@ -151,11 +151,11 @@ All callbacks have default no-op implementations. Override only the phases your Every lifecycle callback returns an `Action` that controls what happens next. -| Action | Meaning | -| -------------------------------- | --------------------------------------------------------------------------- | -| `Action::Continue` | Pass the request or response through to the next stage | -| `Action::Pause` | Stop processing; used after `send_http_response` to short-circuit origin | -| `Action::StopIterationAndBuffer` | Buffer the current body chunk; continue accumulating until `end_of_stream` | +| Action | Meaning | +| -------------------------------- | -------------------------------------------------------------------------- | +| `Action::Continue` | Pass the request or response through to the next stage | +| `Action::Pause` | Stop processing; used after `send_http_response` to short-circuit origin | +| `Action::StopIterationAndBuffer` | Buffer the current body chunk; continue accumulating until `end_of_stream` | For body callbacks, return `Action::StopIterationAndBuffer` until `end_of_stream` is `true`, then process the full body and return `Action::Continue`. @@ -298,24 +298,26 @@ impl HttpContext for MyApp { CDN apps access request metadata through `self.get_property(vec![...])`. The return type is `Option>`. -| Property | Encoding | Description | -| ---------------------- | --------------------- | ------------------------------------------------------ | -| `request.path` | UTF-8 string | URL path | -| `request.query` | UTF-8 string | Query string | -| `request.url` | UTF-8 string | Full request URL | -| `request.host` | UTF-8 string | Domain (may have `shield_` prefix on edge shield nodes) | -| `request.scheme` | UTF-8 string | HTTP scheme (from X-Forwarded-Proto) | -| `request.extension` | UTF-8 string | File extension | -| `request.x_real_ip` | UTF-8 string | Client IP address | -| `request.country` | UTF-8 string | 2-letter ISO country code (geo-IP lookup) | -| `request.country.name` | UTF-8 string | Full country name | -| `request.city` | UTF-8 string | City name | -| `request.region` | UTF-8 string | Region/state | -| `request.continent` | UTF-8 string | Continent | -| `request.asn` | UTF-8 string | Autonomous System Number | -| `request.geo.lat` | UTF-8 string | Latitude | -| `request.geo.long` | UTF-8 string | Longitude | -| `response.status` | 2-byte big-endian u16 | Response status code (response phase only) | +**Path format:** Always pass the property identifier as a single dotted string in a one-element vec — e.g., `vec!["request.path"]`, `vec!["response.status"]`, `vec!["request.geo.long"]`. Do **not** split on dots (e.g., `vec!["response", "status"]` is incorrect). + +| Property | Encoding | Description | +| ---------------------- | --------------------- | -------------------------------------------------------------------------------- | +| `request.path` | UTF-8 string | URL path | +| `request.query` | UTF-8 string | Query string | +| `request.url` | UTF-8 string | Full request URL | +| `request.host` | UTF-8 string | Domain (may have `shield_` prefix on edge shield nodes) | +| `request.scheme` | UTF-8 string | HTTP scheme (from X-Forwarded-Proto) | +| `request.extension` | UTF-8 string | File extension | +| `request.x_real_ip` | UTF-8 string | Client IP address | +| `request.country` | UTF-8 string | 2-letter ISO country code (geo-IP) | +| `request.country.name` | UTF-8 string | Full country name | +| `request.city` | UTF-8 string | City name | +| `request.region` | UTF-8 string | Region/state | +| `request.continent` | UTF-8 string | Continent | +| `request.asn` | UTF-8 string | Autonomous System Number | +| `request.geo.lat` | UTF-8 string | Latitude | +| `request.geo.long` | UTF-8 string | Longitude | +| `response.status` | 2-byte big-endian u16 | Response status code (**binary, NOT a string** — decode with `u16::from_be_bytes`) | Most properties are UTF-8 strings decoded with `std::str::from_utf8()`. The `response.status` property is binary-encoded and must be decoded as a big-endian `u16`. Do not use `String::from_utf8` for this property. @@ -329,7 +331,7 @@ Geo-IP properties (`request.country`, `request.country.name`, `request.city`, `r impl HttpContext for MyApp { fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action { // response.status is a 2-byte big-endian u16 — do NOT use String::from_utf8 - if let Some(bytes) = self.get_property(vec!["response", "status"]) { + if let Some(bytes) = self.get_property(vec!["response.status"]) { if bytes.len() == 2 { let status = u16::from_be_bytes([bytes[0], bytes[1]]); println!("upstream status: {}", status); @@ -375,15 +377,15 @@ Provides persistent key-value storage. The API shape mirrors `fastedge::key_valu pub struct Store { /* ... */ } ``` -| Method | Return Type | Description | -| --------------------------------------------------------- | ------------------------------------ | ------------------------------------------------------ | -| `Store::new()` | `Result` | Open the default store | -| `Store::open(name: &str)` | `Result` | Open a named store | -| `Store::get(key: &str)` | `Result>, Error>` | Get the value for a key; `None` if key does not exist | -| `Store::scan(pattern: &str)` | `Result, Error>` | List keys matching a glob-style pattern | -| `Store::zrange_by_score(key: &str, min: f64, max: f64)` | `Result, f64)>, Error>` | Get sorted-set members with scores between min and max | -| `Store::zscan(key: &str, pattern: &str)` | `Result, f64)>, Error>` | Scan sorted-set members matching a pattern | -| `Store::bf_exists(key: &str, item: &str)` | `Result` | Test whether an item is in a Bloom filter | +| Method | Return Type | Description | +| ------------------------------------------------------- | ------------------------------------ | ------------------------------------------------------ | +| `Store::new()` | `Result` | Open the default store | +| `Store::open(name: &str)` | `Result` | Open a named store | +| `Store::get(key: &str)` | `Result>, Error>` | Get the value for a key; `None` if key does not exist | +| `Store::scan(pattern: &str)` | `Result, Error>` | List keys matching a glob-style pattern | +| `Store::zrange_by_score(key: &str, min: f64, max: f64)` | `Result, f64)>, Error>` | Get sorted-set members with scores between min and max | +| `Store::zscan(key: &str, pattern: &str)` | `Result, f64)>, Error>` | Scan sorted-set members matching a pattern | +| `Store::bf_exists(key: &str, item: &str)` | `Result` | Test whether an item is in a Bloom filter | #### `Error` @@ -395,11 +397,11 @@ pub enum Error { } ``` -| Variant | Description | -| --------------- | ------------------------------------------------------------ | -| `NoSuchStore` | The store label is not recognized by the host | -| `AccessDenied` | The application does not have access to the specified store | -| `Other(String)` | An implementation-specific error (e.g., I/O failure) | +| Variant | Description | +| --------------- | ----------------------------------------------------------- | +| `NoSuchStore` | The store label is not recognized by the host | +| `AccessDenied` | The application does not have access to the specified store | +| `Other(String)` | An implementation-specific error (e.g., I/O failure) | #### Example — Bloom filter check in request headers phase diff --git a/docs/HOST_SERVICES.md b/docs/HOST_SERVICES.md index c802b25..5ce7587 100644 --- a/docs/HOST_SERVICES.md +++ b/docs/HOST_SERVICES.md @@ -76,11 +76,11 @@ Scans the store for keys matching a glob-style pattern. Returns a list of matchi Supported glob syntax: -| Pattern | Matches | -| --------- | ------------------------------------------- | -| `*` | Any sequence of characters within a segment | -| `?` | Any single character | -| `[abc]` | Any character in the set | +| Pattern | Matches | +| ------- | ------------------------------------------- | +| `*` | Any sequence of characters within a segment | +| `?` | Any single character | +| `[abc]` | Any character in the set | ```rust,no_run use fastedge::key_value::Store; @@ -345,14 +345,14 @@ async fn main(_request: Request) -> anyhow::Result> { ### When to Use Dictionary vs Key-Value vs Secrets -| Criterion | `dictionary` | `key_value` | `secret` | -| ---------------------------- | -------------------------------------- | ----------------------------------------- | ------------------------------------------- | -| **Mutability** | Read-only; set at deployment time | Read-only from application code | Read-only; managed by platform | -| **Value type** | UTF-8 strings only | Arbitrary bytes | Arbitrary bytes | -| **Advanced data structures** | No | Sorted sets, bloom filters, glob scan | No | -| **Confidentiality** | Not encrypted; visible in config | Not encrypted at the application layer | Encrypted at rest; access-controlled | -| **Typical use cases** | Feature flags, routing config, tuning | Caching, counters, state, rate-limit data | API keys, tokens, certificates, credentials | -| **Versioning / rotation** | No | No | Yes, via `get_effective_at` | +| Criterion | `dictionary` | `key_value` | `secret` | +| ---------------------------- | ------------------------------------- | ----------------------------------------- | ------------------------------------------- | +| **Mutability** | Read-only; set at deployment time | Read-only from application code | Read-only; managed by platform | +| **Value type** | UTF-8 strings only | Arbitrary bytes | Arbitrary bytes | +| **Advanced data structures** | No | Sorted sets, bloom filters, glob scan | No | +| **Confidentiality** | Not encrypted; visible in config | Not encrypted at the application layer | Encrypted at rest; access-controlled | +| **Typical use cases** | Feature flags, routing config, tuning | Caching, counters, state, rate-limit data | API keys, tokens, certificates, credentials | +| **Versioning / rotation** | No | No | Yes, via `get_effective_at` | Use `dictionary` for simple, non-sensitive string configuration that is known at deployment time. Use `key_value` for larger datasets, binary values, or data that requires advanced query patterns. Use `secret` for any value that must be kept confidential. diff --git a/docs/INDEX.md b/docs/INDEX.md index 0e88e9f..a519dc0 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -4,12 +4,12 @@ Documentation for the `fastedge` crate (v0.3.5) — a Rust SDK for building edge ## Documents -| File | Description | -| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| [quickstart.md](quickstart.md) | Getting started: project setup, writing a handler, building to WASM (`wasm32-wasip2` for async, `wasm32-wasip1` for basic/CDN) | -| [SDK_API.md](SDK_API.md) | Core API: handler macros (`#[wstd::http_server]`, `#[fastedge::http]`), Body type, outbound HTTP (`send_request`), errors, feature flags | -| [HOST_SERVICES.md](HOST_SERVICES.md) | Host services for HTTP apps: key-value store, secrets, dictionary, diagnostics | -| [CDN_APPS.md](CDN_APPS.md) | CDN apps: proxy-wasm lifecycle, `fastedge::proxywasm::*` API surface, request/response manipulation | +| File | Description | +| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | +| [quickstart.md](quickstart.md) | Getting started: project setup, writing a handler, building to WASM (`wasm32-wasip2` for async, `wasm32-wasip1` for basic/CDN) | +| [SDK_API.md](SDK_API.md) | Core API: handler macros (`#[wstd::http_server]`, `#[fastedge::http]`), Body type, outbound HTTP (`send_request`), errors, feature flags | +| [HOST_SERVICES.md](HOST_SERVICES.md) | Host services for HTTP apps: key-value store, secrets, dictionary, diagnostics | +| [CDN_APPS.md](CDN_APPS.md) | CDN apps: proxy-wasm lifecycle, `fastedge::proxywasm::*` API surface, request/response manipulation | ## Suggested Reading Order diff --git a/docs/SDK_API.md b/docs/SDK_API.md index edfde75..996f7b6 100644 --- a/docs/SDK_API.md +++ b/docs/SDK_API.md @@ -25,8 +25,8 @@ For `#[fastedge::http]` (basic): ```toml [dependencies] -fastedge = "0.3" -anyhow = "1.0" +fastedge = "0.3.5" +anyhow = "1" [lib] crate-type = ["cdylib"] @@ -410,14 +410,14 @@ Enable non-default features in `Cargo.toml`: ```toml [dependencies] -fastedge = { version = "0.3", features = ["json"] } +fastedge = { version = "0.3.5", features = ["json"] } ``` Disable the default `proxywasm` feature if you do not need it: ```toml [dependencies] -fastedge = { version = "0.3", default-features = false } +fastedge = { version = "0.3.5", default-features = false } ``` --- diff --git a/fastedge-plugin-source/.generation-config.md b/fastedge-plugin-source/.generation-config.md index 7d0edb2..67bde53 100644 --- a/fastedge-plugin-source/.generation-config.md +++ b/fastedge-plugin-source/.generation-config.md @@ -398,7 +398,7 @@ Do NOT document or suggest: **Known limitation — header removal:** On the FastEdge CDN (nginx-based), removing a header via the proxy-wasm API sets its value to an empty string rather than fully removing it. Keep this in mind when checking for header presence. **Request Properties:** -CDN apps access request metadata via `self.get_property(vec![...])`. Document ALL available properties on the FastEdge platform: +CDN apps access request metadata via `self.get_property(vec![...])`. **Path format:** Always pass the property identifier as a single dotted string in a one-element vec — e.g., `vec!["request.path"]`, `vec!["response.status"]`, `vec!["request.geo.long"]`. Do **not** split on dots (e.g., `vec!["response", "status"]` is incorrect). Document ALL available properties on the FastEdge platform: | Property | Encoding | Description | | ---------------------- | --------------------- | ------------------------------------------------------------------------------- |