diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ce9f661..dd40ac1 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -62,6 +62,8 @@ Files in `docs/` are **machine-generated** from source code by `./fastedge-plugi | `src/proxywasm/dictionary.rs` | `docs/HOST_SERVICES.md` | | `src/proxywasm/utils.rs` | `docs/HOST_SERVICES.md` | | `src/proxywasm/` (CDN lifecycle, FFI) | `docs/CDN_APPS.md` | +| `examples/http/wasi/hello_world/src/lib.rs` | `docs/quickstart.md` | +| `examples/http/basic/hello_world/src/lib.rs` | `docs/quickstart.md` | | `Cargo.toml` (version, features) | `docs/INDEX.md` | | `fastedge-plugin-source/manifest.json` | `.github/copilot-instructions.md` | diff --git a/.gitignore b/.gitignore index 79085ea..614a059 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,16 @@ target/ # FastEdge debugger artifacts **/.fastedge-debug/ + +# build artifacts +**/*.wasm + +# example project lock files +examples/**/pnpm-lock.yaml +examples/**/package-lock.json +examples/**/livetest.config.json + + +# Doc-generator failure artifacts — rejected/preamble-leaked claude -p +# outputs preserved for prompt-debugging. Prune manually. +docs/.failures/ diff --git a/context/architecture/HOST_SDK_CONTRACT.md b/context/architecture/HOST_SDK_CONTRACT.md index f6f9107..de1c061 100644 --- a/context/architecture/HOST_SDK_CONTRACT.md +++ b/context/architecture/HOST_SDK_CONTRACT.md @@ -120,11 +120,13 @@ The `#[fastedge::http]` macro generates the `Guest` trait implementation that br | `proxy_on_request_body(context_id, body_size, end_of_stream)` | Handle request body phase | | `proxy_on_response_headers(context_id, num_headers)` | Handle response headers phase | | `proxy_on_response_body(context_id, body_size, end_of_stream)` | Handle response body phase | -| `proxy_on_log(context_id)` | Final logging callback | +| `proxy_on_log(context_id)` | Final logging callback — **NOT dispatched on FastEdge today** (see note below) | | `proxy_on_http_call_response(ctx, call_id, h_size, b_size, t_size)` | HTTP callout response delivered | See `architecture/REQUEST_LIFECYCLE.md` for the order these are called. +**`proxy_on_log` is part of the proxy-wasm spec but the FastEdge platform does not currently invoke it.** Neither the edge runtime nor the local debugger (`@gcoredev/fastedge-test`) dispatches the symbol. The SDK still exports it for forward-compat. Examples and production code must not rely on `on_log` firing — use the four phase hooks (`on_request_*`, `on_response_*`) for end-of-request observability instead. + --- ## Execution Constraints diff --git a/context/architecture/REQUEST_LIFECYCLE.md b/context/architecture/REQUEST_LIFECYCLE.md index cabfa22..7c13afa 100644 --- a/context/architecture/REQUEST_LIFECYCLE.md +++ b/context/architecture/REQUEST_LIFECYCLE.md @@ -62,11 +62,13 @@ CDN-mode apps using the proxy-wasm interface have a multi-phase lifecycle. The h | 7. Response Body proxy_on_response_body(ctx, size, end_of_stream) | -8. Log proxy_on_log(ctx) +8. Log proxy_on_log(ctx) // NOT dispatched on FastEdge today (see note below) | 9. Instance discarded ``` +> **NOTE on `proxy_on_log`:** The hook is part of the proxy-wasm spec but the FastEdge platform does not currently invoke it (verified in both the edge runtime and `@gcoredev/fastedge-test`). The SDK still exports the symbol for forward-compat. Place end-of-request observability into the four phase hooks (`on_request_headers`/`on_request_body`/`on_response_headers`/`on_response_body`) — typically `on_response_body` with `end_of_stream = true` is the closest functional substitute. + ### Actions Each phase handler returns an action code that controls flow: diff --git a/docs/CDN_APPS.md b/docs/CDN_APPS.md index 9ff7bcd..cb4edb0 100644 --- a/docs/CDN_APPS.md +++ b/docs/CDN_APPS.md @@ -54,7 +54,7 @@ crate-type = ["cdylib"] [dependencies] proxy-wasm = "0.2" -fastedge = { version = "0.3", features = ["proxywasm"] } +fastedge = { version = "0.4", features = ["proxywasm"] } ``` The `proxywasm` feature flag is required to access `fastedge::proxywasm::*`. Without it, `fastedge` only exposes Component Model APIs, which are not available in the proxy-wasm environment. @@ -141,6 +141,9 @@ proxy_wasm::main! {{ Box::new(MyAppRoot) }); }} +# struct MyAppRoot; +# impl proxy_wasm::traits::Context for MyAppRoot {} +# impl proxy_wasm::traits::RootContext for MyAppRoot {} ``` ### Root Context @@ -150,6 +153,9 @@ The root context is a singleton created once when the filter loads. Its primary ```rust,no_run # use proxy_wasm::traits::*; # use proxy_wasm::types::*; +# struct MyApp; +# impl Context for MyApp {} +# impl HttpContext for MyApp {} struct MyAppRoot; impl Context for MyAppRoot {} diff --git a/docs/INDEX.md b/docs/INDEX.md index a519dc0..7773ed2 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -1,6 +1,6 @@ # FastEdge Rust SDK Documentation -Documentation for the `fastedge` crate (v0.3.5) — a Rust SDK for building edge computing applications that compile to WebAssembly and run on the FastEdge platform. +Documentation for the `fastedge` crate (v0.4.0) — a Rust SDK for building edge computing applications that compile to WebAssembly and run on the FastEdge platform. ## Documents diff --git a/docs/SDK_API.md b/docs/SDK_API.md index fb4ad9f..9d60fdb 100644 --- a/docs/SDK_API.md +++ b/docs/SDK_API.md @@ -8,7 +8,7 @@ Reference for the `fastedge` crate. Covers the handler macros, body type, outbou ### Cargo.toml -The current crate version is `0.3.5` (from `[workspace.package]` in the repository's `Cargo.toml`). +The current crate version is `0.4.0` (from `[workspace.package]` in the repository's `Cargo.toml`). For `#[wstd::http_server]` (recommended): @@ -25,7 +25,7 @@ For `#[fastedge::http]` (basic): ```toml [dependencies] -fastedge = "0.3.5" +fastedge = "0.4.0" anyhow = "1" [lib] @@ -215,14 +215,14 @@ pub struct Body { /* private fields */ } ### Constructors -| Constructor | Content-Type | Notes | -| -------------------------------------------- | --------------------------- | ------------------------------------------------------------------ | -| `Body::from(value: String)` | `text/plain; charset=utf-8` | | -| `Body::from(value: &'static str)` | `text/plain; charset=utf-8` | | -| `Body::from(value: Vec)` | `application/octet-stream` | | -| `Body::from(value: &'static [u8])` | `application/octet-stream` | | -| `Body::empty()` | `text/plain; charset=utf-8` | Zero-length body | -| `Body::try_from(value: serde_json::Value)` | `application/json` | Requires `json` feature; returns `Result` | +| Constructor | Content-Type | Notes | +| ------------------------------------------ | --------------------------- | ------------------------------------------------------------------ | +| `Body::from(value: String)` | `text/plain; charset=utf-8` | | +| `Body::from(value: &'static str)` | `text/plain; charset=utf-8` | | +| `Body::from(value: Vec)` | `application/octet-stream` | | +| `Body::from(value: &'static [u8])` | `application/octet-stream` | | +| `Body::empty()` | `text/plain; charset=utf-8` | Zero-length body | +| `Body::try_from(value: serde_json::Value)` | `application/json` | Requires `json` feature; returns `Result` | ```rust use fastedge::body::Body; @@ -233,7 +233,7 @@ let bytes = Body::from(vec![0x48u8, 0x69]); let empty = Body::empty(); ``` -```rust +```rust,ignore // json feature required use fastedge::body::Body; use serde_json::json; @@ -247,10 +247,10 @@ assert_eq!(body.content_type(), "application/json"); ### Methods -| Method | Return Type | Description | -| -------------------------------- | ----------- | --------------------------------------------------- | -| `content_type(&self) -> String` | `String` | Returns the MIME type set when the body was created | -| `empty() -> Self` | `Body` | Constructs a zero-length body | +| Method | Return Type | Description | +| ------------------------------- | ----------- | --------------------------------------------------- | +| `content_type(&self) -> String` | `String` | Returns the MIME type set when the body was created | +| `empty() -> Self` | `Body` | Constructs a zero-length body | All methods from `bytes::Bytes` are available via `Deref`: @@ -267,16 +267,16 @@ let slice: &[u8] = &body[..]; Content-type is determined at construction time and cannot be changed after creation. -| Input type | Resulting content-type | -| --------------------- | --------------------------- | -| `String` / `&str` | `text/plain; charset=utf-8` | -| `Vec` / `&[u8]` | `application/octet-stream` | -| `serde_json::Value` | `application/json` | -| `Body::empty()` | `text/plain; charset=utf-8` | +| Input type | Resulting content-type | +| ------------------- | --------------------------- | +| `String` / `&str` | `text/plain; charset=utf-8` | +| `Vec` / `&[u8]` | `application/octet-stream` | +| `serde_json::Value` | `application/json` | +| `Body::empty()` | `text/plain; charset=utf-8` | To send a response with a content-type that does not match automatic detection, set the `Content-Type` header explicitly on the response builder: -```rust +```rust,no_run use fastedge::body::Body; use fastedge::http::{Response, StatusCode}; @@ -370,17 +370,17 @@ pub enum Error { } ``` -| Variant | When it occurs | -| ----------------------------------- | ---------------------------------------------------------------------------------------------------- | -| `UnsupportedMethod(http::Method)` | `send_request` was called with a method other than GET, POST, PUT, DELETE, HEAD, PATCH, or OPTIONS | -| `BindgenHttpError` | The host runtime returned an error during request execution | -| `HttpError(http::Error)` | An error occurred constructing or parsing an HTTP message | -| `InvalidBody` | The request or response body could not be encoded or decoded | -| `InvalidStatusCode(u16)` | A status code outside the range 100–599 was encountered | +| Variant | When it occurs | +| --------------------------------- | -------------------------------------------------------------------------------------------------- | +| `UnsupportedMethod(http::Method)` | `send_request` was called with a method other than GET, POST, PUT, DELETE, HEAD, PATCH, or OPTIONS | +| `BindgenHttpError` | The host runtime returned an error during request execution | +| `HttpError(http::Error)` | An error occurred constructing or parsing an HTTP message | +| `InvalidBody` | The request or response body could not be encoded or decoded | +| `InvalidStatusCode(u16)` | A status code outside the range 100–599 was encountered | `Error` implements `std::error::Error` and `std::fmt::Display`. It is compatible with `anyhow` and `?` propagation. -```rust +```rust,no_run use fastedge::{Error, send_request}; use fastedge::body::Body; use fastedge::http::{Method, Request}; @@ -401,23 +401,23 @@ fn fetch(uri: &str) -> Result { ## Feature Flags -| Flag | Default | Effect | -| ------------- | ---------- | ----------------------------------------------------------------------------------- | -| `proxywasm` | enabled | Enables the `fastedge::proxywasm` module for ProxyWasm ABI compatibility | -| `json` | disabled | Enables `Body::try_from(serde_json::Value)` and adds `serde_json` as a dependency | +| Flag | Default | Effect | +| ----------- | -------- | ---------------------------------------------------------------------------------- | +| `proxywasm` | enabled | Enables the `fastedge::proxywasm` module for ProxyWasm ABI compatibility | +| `json` | disabled | Enables `Body::try_from(serde_json::Value)` and adds `serde_json` as a dependency | Enable non-default features in `Cargo.toml`: ```toml [dependencies] -fastedge = { version = "0.3.5", features = ["json"] } +fastedge = { version = "0.4.0", features = ["json"] } ``` Disable the default `proxywasm` feature if you do not need it: ```toml [dependencies] -fastedge = { version = "0.3.5", default-features = false } +fastedge = { version = "0.4.0", default-features = false } ``` --- @@ -432,15 +432,15 @@ use fastedge::http::{Method, Request, Response, StatusCode, HeaderMap, Uri}; **Supported HTTP methods** (the complete set accepted by `send_request`): -| Constant | Method | -| ------------------- | ----------- | -| `Method::GET` | `GET` | -| `Method::POST` | `POST` | -| `Method::PUT` | `PUT` | -| `Method::DELETE` | `DELETE` | -| `Method::HEAD` | `HEAD` | -| `Method::PATCH` | `PATCH` | -| `Method::OPTIONS` | `OPTIONS` | +| Constant | Method | +| ----------------- | --------- | +| `Method::GET` | `GET` | +| `Method::POST` | `POST` | +| `Method::PUT` | `PUT` | +| `Method::DELETE` | `DELETE` | +| `Method::HEAD` | `HEAD` | +| `Method::PATCH` | `PATCH` | +| `Method::OPTIONS` | `OPTIONS` | --- diff --git a/docs/quickstart.md b/docs/quickstart.md index 418dda5..f1041b1 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -88,7 +88,7 @@ Add dependencies to `Cargo.toml`: ```toml [dependencies] -fastedge = "0.3.5" +fastedge = "0.4.0" anyhow = "1" [lib] @@ -124,10 +124,10 @@ The compiled `.wasm` file is written to `target/wasm32-wasip1/release/`. ## Build -| Handler path | Build command | -| ----------------------- | ---------------------------------------------- | -| Async (`wstd`) | `cargo build --target wasm32-wasip2 --release` | -| Sync (`fastedge::http`) | `cargo build --target wasm32-wasip1 --release` | +| Handler path | Build command | +| ----------------------- | ------------------------------------------------ | +| Async (`wstd`) | `cargo build --target wasm32-wasip2 --release` | +| Sync (`fastedge::http`) | `cargo build --target wasm32-wasip1 --release` | Both commands produce a `.wasm` binary in the respective `target//release/` directory. Neither path requires `cargo-component`. @@ -142,7 +142,7 @@ Enable the `json` feature in `Cargo.toml`: ```toml [dependencies] -fastedge = { version = "0.3.5", features = ["json"] } +fastedge = { version = "0.4.0", features = ["json"] } ``` ## CDN Apps diff --git a/examples/cdn/ab_testing/fixtures/existing-cookie-a.live.json b/examples/cdn/ab_testing/fixtures/existing-cookie-a.live.json new file mode 100644 index 0000000..02547df --- /dev/null +++ b/examples/cdn/ab_testing/fixtures/existing-cookie-a.live.json @@ -0,0 +1,10 @@ +{ + "expected": { + "logs": ["A/B test \"homepage-hero\": variant A, path /a/landing"], + "headers": { + "set-cookie": "fe_exp_homepage-hero=A; Path=/; Max-Age=86400; SameSite=Lax", + "x-variant": "A" + }, + "status": 200 + } +} diff --git a/examples/cdn/ab_testing/fixtures/existing-cookie-b.live.json b/examples/cdn/ab_testing/fixtures/existing-cookie-b.live.json new file mode 100644 index 0000000..8cc50a1 --- /dev/null +++ b/examples/cdn/ab_testing/fixtures/existing-cookie-b.live.json @@ -0,0 +1,10 @@ +{ + "expected": { + "logs": ["A/B test \"homepage-hero\": variant B, path /b/landing"], + "headers": { + "set-cookie": "fe_exp_homepage-hero=B; Path=/; Max-Age=86400; SameSite=Lax", + "x-variant": "B" + }, + "status": 200 + } +} diff --git a/examples/cdn/ab_testing/fixtures/missing-config.live.json b/examples/cdn/ab_testing/fixtures/missing-config.live.json new file mode 100644 index 0000000..71a4bc5 --- /dev/null +++ b/examples/cdn/ab_testing/fixtures/missing-config.live.json @@ -0,0 +1,6 @@ +{ + "expected": { + "status": 500, + "body": "App misconfigured - EXPERIMENT_NAME must be set" + } +} diff --git a/examples/cdn/ab_testing/fixtures/new-visitor.live.json b/examples/cdn/ab_testing/fixtures/new-visitor.live.json new file mode 100644 index 0000000..ab930a2 --- /dev/null +++ b/examples/cdn/ab_testing/fixtures/new-visitor.live.json @@ -0,0 +1,9 @@ +{ + "expected": { + "logs": ["A/B test \"homepage-hero\": variant "], + "headers": { + "set-cookie": { "contains": "fe_exp_homepage-hero=" } + }, + "status": 200 + } +} diff --git a/examples/cdn/ab_testing/src/lib.rs b/examples/cdn/ab_testing/src/lib.rs index 4d1c09d..27adb8a 100644 --- a/examples/cdn/ab_testing/src/lib.rs +++ b/examples/cdn/ab_testing/src/lib.rs @@ -108,14 +108,10 @@ impl HttpContext for AbTestingContext { self.add_http_request_header("X-Experiment", &experiment_name); self.add_http_request_header("X-Variant", &assigned); - proxy_wasm::hostcalls::log( - LogLevel::Info, - &format!( - "A/B test \"{}\": variant {}, path {}", - experiment_name, assigned, new_path - ), - ) - .ok(); + println!( + "A/B test \"{}\": variant {}, path {}", + experiment_name, assigned, new_path + ); Action::Continue } diff --git a/examples/cdn/api_key/fixtures/happy-path.live.json b/examples/cdn/api_key/fixtures/happy-path.live.json new file mode 100644 index 0000000..cf47e4a --- /dev/null +++ b/examples/cdn/api_key/fixtures/happy-path.live.json @@ -0,0 +1,6 @@ +{ + "expected": { + "logs": ["API key validated successfully"], + "status": 200 + } +} diff --git a/examples/cdn/api_key/fixtures/invalid-key.live.json b/examples/cdn/api_key/fixtures/invalid-key.live.json new file mode 100644 index 0000000..c5a370e --- /dev/null +++ b/examples/cdn/api_key/fixtures/invalid-key.live.json @@ -0,0 +1,7 @@ +{ + "expected": { + "logs": ["API key validation failed"], + "status": 403, + "body": "Invalid API key" + } +} diff --git a/examples/cdn/api_key/fixtures/missing-header.live.json b/examples/cdn/api_key/fixtures/missing-header.live.json new file mode 100644 index 0000000..ec0e97a --- /dev/null +++ b/examples/cdn/api_key/fixtures/missing-header.live.json @@ -0,0 +1,9 @@ +{ + "expected": { + "status": 401, + "headers": { + "www-authenticate": "API-Key" + }, + "body": "Missing X-API-Key header" + } +} diff --git a/examples/cdn/api_key/fixtures/missing-secret.live.json b/examples/cdn/api_key/fixtures/missing-secret.live.json new file mode 100644 index 0000000..c501089 --- /dev/null +++ b/examples/cdn/api_key/fixtures/missing-secret.live.json @@ -0,0 +1,6 @@ +{ + "expected": { + "status": 500, + "body": "App misconfigured" + } +} diff --git a/examples/cdn/api_key/src/lib.rs b/examples/cdn/api_key/src/lib.rs index 509c202..6484e61 100644 --- a/examples/cdn/api_key/src/lib.rs +++ b/examples/cdn/api_key/src/lib.rs @@ -68,7 +68,7 @@ impl HttpContext for ApiKeyContext { }; if provided_key != expected_key { - proxy_wasm::hostcalls::log(LogLevel::Info, "API key validation failed").ok(); + println!("API key validation failed"); self.send_http_response(403, vec![], Some(b"Invalid API key")); return Action::Pause; } @@ -76,7 +76,7 @@ impl HttpContext for ApiKeyContext { // Strip the API key header before forwarding to upstream self.set_http_request_header("X-API-Key", None); - proxy_wasm::hostcalls::log(LogLevel::Info, "API key validated successfully").ok(); + println!("API key validated successfully"); Action::Continue } } diff --git a/examples/cdn/body/fixtures/client.live.json b/examples/cdn/body/fixtures/client.live.json new file mode 100644 index 0000000..16790f3 --- /dev/null +++ b/examples/cdn/body/fixtures/client.live.json @@ -0,0 +1,14 @@ +{ + "expected": { + "status": 200, + "headers": { + "transfer-encoding": "Chunked" + }, + "logs": [ + "url=http://fastedge-builtin.debug", + "content_type=text/plain" + ], + "bodyContains": "Original message body (52 bytes) redacted.", + "noLogs": ["error"] + } +} diff --git a/examples/cdn/body/fixtures/skip.live.json b/examples/cdn/body/fixtures/skip.live.json new file mode 100644 index 0000000..2e3cd54 --- /dev/null +++ b/examples/cdn/body/fixtures/skip.live.json @@ -0,0 +1,14 @@ +{ + "expected": { + "status": 200, + "headers": { + "transfer-encoding": "Chunked" + }, + "logs": [ + "url=http://fastedge-builtin.debug", + "content_type=text/plain" + ], + "body": "Hello World, this is a test message", + "noLogs": ["redacted"] + } +} diff --git a/examples/cdn/cache_control/fixtures/error-status.live.json b/examples/cdn/cache_control/fixtures/error-status.live.json new file mode 100644 index 0000000..a5243cf --- /dev/null +++ b/examples/cdn/cache_control/fixtures/error-status.live.json @@ -0,0 +1,11 @@ +{ + "expected": { + "status": 500, + "headers": { + "cache-control": "no-store" + }, + "noLogs": [ + "Cache-Control:" + ] + } +} diff --git a/examples/cdn/cache_control/fixtures/happy-path.live.json b/examples/cdn/cache_control/fixtures/happy-path.live.json new file mode 100644 index 0000000..055fdf7 --- /dev/null +++ b/examples/cdn/cache_control/fixtures/happy-path.live.json @@ -0,0 +1,12 @@ +{ + "expected": { + "status": 200, + "headers": { + "cache-control": "public, max-age=3600, must-revalidate", + "vary": "Accept-Encoding" + }, + "logs": [ + "Cache-Control: public, max-age=3600, must-revalidate (content-type: text/html; charset=utf-8)" + ] + } +} diff --git a/examples/cdn/cache_control/fixtures/happy-path.test.json b/examples/cdn/cache_control/fixtures/happy-path.test.json index a8c949c..e7cd02a 100644 --- a/examples/cdn/cache_control/fixtures/happy-path.test.json +++ b/examples/cdn/cache_control/fixtures/happy-path.test.json @@ -5,13 +5,9 @@ "method": "GET", "url": "http://fastedge-builtin.debug", "headers": { - "host": "example.com" - }, - "body": "" - }, - "response": { - "headers": { - "content-type": "text/html; charset=utf-8" + "host": "example.com", + "content-type": "text/html; charset=utf-8", + "x-debugger-content": "body-only" }, "body": "Hello" }, diff --git a/examples/cdn/cache_control/fixtures/javascript-asset.live.json b/examples/cdn/cache_control/fixtures/javascript-asset.live.json new file mode 100644 index 0000000..2803e55 --- /dev/null +++ b/examples/cdn/cache_control/fixtures/javascript-asset.live.json @@ -0,0 +1,11 @@ +{ + "expected": { + "status": 200, + "headers": { + "cache-control": "public, max-age=31536000, immutable" + }, + "logs": [ + "Cache-Control: public, max-age=31536000, immutable (content-type: application/javascript)" + ] + } +} diff --git a/examples/cdn/cache_control/fixtures/javascript-asset.test.json b/examples/cdn/cache_control/fixtures/javascript-asset.test.json index 7d069df..b1e156e 100644 --- a/examples/cdn/cache_control/fixtures/javascript-asset.test.json +++ b/examples/cdn/cache_control/fixtures/javascript-asset.test.json @@ -5,13 +5,9 @@ "method": "GET", "url": "http://fastedge-builtin.debug/app.js", "headers": { - "host": "example.com" - }, - "body": "" - }, - "response": { - "headers": { - "content-type": "application/javascript" + "host": "example.com", + "content-type": "application/javascript", + "x-debugger-content": "body-only" }, "body": "console.log('hello');" }, diff --git a/examples/cdn/cache_control/fixtures/json-api.live.json b/examples/cdn/cache_control/fixtures/json-api.live.json new file mode 100644 index 0000000..7ea65e0 --- /dev/null +++ b/examples/cdn/cache_control/fixtures/json-api.live.json @@ -0,0 +1,12 @@ +{ + "expected": { + "status": 200, + "headers": { + "cache-control": "no-cache, no-store, must-revalidate", + "vary": "Accept, Authorization" + }, + "logs": [ + "Cache-Control: no-cache, no-store, must-revalidate (content-type: application/json)" + ] + } +} diff --git a/examples/cdn/cache_control/fixtures/static-asset.live.json b/examples/cdn/cache_control/fixtures/static-asset.live.json new file mode 100644 index 0000000..d10a340 --- /dev/null +++ b/examples/cdn/cache_control/fixtures/static-asset.live.json @@ -0,0 +1,11 @@ +{ + "expected": { + "status": 200, + "headers": { + "cache-control": "public, max-age=31536000, immutable" + }, + "logs": [ + "Cache-Control: public, max-age=31536000, immutable (content-type: image/png)" + ] + } +} diff --git a/examples/cdn/cache_control/fixtures/static-asset.test.json b/examples/cdn/cache_control/fixtures/static-asset.test.json index 80648ca..882b177 100644 --- a/examples/cdn/cache_control/fixtures/static-asset.test.json +++ b/examples/cdn/cache_control/fixtures/static-asset.test.json @@ -5,13 +5,9 @@ "method": "GET", "url": "http://fastedge-builtin.debug/logo.png", "headers": { - "host": "example.com" - }, - "body": "" - }, - "response": { - "headers": { - "content-type": "image/png" + "host": "example.com", + "content-type": "image/png", + "x-debugger-content": "body-only" }, "body": "" }, diff --git a/examples/cdn/cache_control/fixtures/unknown-content-type.live.json b/examples/cdn/cache_control/fixtures/unknown-content-type.live.json new file mode 100644 index 0000000..4eeb87e --- /dev/null +++ b/examples/cdn/cache_control/fixtures/unknown-content-type.live.json @@ -0,0 +1,11 @@ +{ + "expected": { + "status": 200, + "headers": { + "cache-control": "public, max-age=600" + }, + "logs": [ + "Cache-Control: public, max-age=600 (content-type: text/csv)" + ] + } +} diff --git a/examples/cdn/cache_control/fixtures/unknown-content-type.test.json b/examples/cdn/cache_control/fixtures/unknown-content-type.test.json index efc021b..f75b8d5 100644 --- a/examples/cdn/cache_control/fixtures/unknown-content-type.test.json +++ b/examples/cdn/cache_control/fixtures/unknown-content-type.test.json @@ -5,13 +5,9 @@ "method": "GET", "url": "http://fastedge-builtin.debug/data.csv", "headers": { - "host": "example.com" - }, - "body": "" - }, - "response": { - "headers": { - "content-type": "text/csv" + "host": "example.com", + "content-type": "text/csv", + "x-debugger-content": "body-only" }, "body": "col1,col2\nval1,val2" }, diff --git a/examples/cdn/cache_control/fixtures/xml-api.live.json b/examples/cdn/cache_control/fixtures/xml-api.live.json new file mode 100644 index 0000000..7ea65e0 --- /dev/null +++ b/examples/cdn/cache_control/fixtures/xml-api.live.json @@ -0,0 +1,12 @@ +{ + "expected": { + "status": 200, + "headers": { + "cache-control": "no-cache, no-store, must-revalidate", + "vary": "Accept, Authorization" + }, + "logs": [ + "Cache-Control: no-cache, no-store, must-revalidate (content-type: application/json)" + ] + } +} diff --git a/examples/cdn/cache_control/src/lib.rs b/examples/cdn/cache_control/src/lib.rs index 90c720a..a10b708 100644 --- a/examples/cdn/cache_control/src/lib.rs +++ b/examples/cdn/cache_control/src/lib.rs @@ -88,14 +88,10 @@ impl HttpContext for CacheControlContext { self.set_http_response_header("Cache-Control", Some(&cache_control)); - proxy_wasm::hostcalls::log( - LogLevel::Info, - &format!( - "Cache-Control: {} (content-type: {})", - cache_control, content_type - ), - ) - .ok(); + println!( + "Cache-Control: {} (content-type: {})", + cache_control, content_type + ); Action::Continue } diff --git a/examples/cdn/convert_image/Cargo.lock b/examples/cdn/convert_image/Cargo.lock new file mode 100644 index 0000000..ca6665b --- /dev/null +++ b/examples/cdn/convert_image/Cargo.lock @@ -0,0 +1,1024 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bitstream-io" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] + +[[package]] +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "convert_image" +version = "0.1.0" +dependencies = [ + "image", + "proxy-wasm", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2" + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "proxy-wasm" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8d35d9e2bc5104e2e954b149aa1d5f9fa3bb27f73b45b2706020fed101db685" +dependencies = [ + "hashbrown", + "log", +] + +[[package]] +name = "pxfm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] diff --git a/examples/cdn/convert_image/fixtures/.env b/examples/cdn/convert_image/fixtures/.env new file mode 100644 index 0000000..3d9c151 --- /dev/null +++ b/examples/cdn/convert_image/fixtures/.env @@ -0,0 +1,2 @@ +FASTEDGE_VAR_ENV_FORMATS_TO_TRANSFORM=jpg,png +FASTEDGE_VAR_ENV_IGNORED_UA_LIST=Googlebot,bingbot diff --git a/examples/cdn/convert_image/fixtures/conversion-scheduled.live.json b/examples/cdn/convert_image/fixtures/conversion-scheduled.live.json new file mode 100644 index 0000000..fb88971 --- /dev/null +++ b/examples/cdn/convert_image/fixtures/conversion-scheduled.live.json @@ -0,0 +1,9 @@ +{ + "expected": { + "logs": [" bytes -> ", "image/avif"], + "headers": { + "Content-Type": "image/avif" + }, + "noLogs": ["not transforming", "cannot load image"] + } +} diff --git a/examples/cdn/convert_image/fixtures/conversion-scheduled.test.json b/examples/cdn/convert_image/fixtures/conversion-scheduled.test.json new file mode 100644 index 0000000..b980e14 --- /dev/null +++ b/examples/cdn/convert_image/fixtures/conversion-scheduled.test.json @@ -0,0 +1,31 @@ +{ + "appType": "proxy-wasm", + "description": "All conditions met — extension png in list, valid User-Agent — Image-Format: image/avif set; empty body causes graceful decode failure (logs 'cannot load image')", + "request": { + "method": "GET", + "url": "http://fastedge-builtin.debug/418.png", + "headers": { + "host": "example.com", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" + }, + "body": "" + }, + "response": { + "headers": { + "content-type": "image/png" + }, + "body": "" + }, + "properties": { + "request.extension": "png" + }, + "dotenv": { + "enabled": true, + "path": "." + }, + "logLevel": 2, + "wasm": { + "path": "/.fastedge-debug/app.wasm", + "description": "Default debugger WASM binary" + } +} diff --git a/examples/cdn/convert_image/fixtures/extension-not-in-list.live.json b/examples/cdn/convert_image/fixtures/extension-not-in-list.live.json new file mode 100644 index 0000000..ed0bcc7 --- /dev/null +++ b/examples/cdn/convert_image/fixtures/extension-not-in-list.live.json @@ -0,0 +1,5 @@ +{ + "expected": { + "logs": ["is not in the list of formats to transform"] + } +} diff --git a/examples/cdn/convert_image/fixtures/extension-not-in-list.test.json b/examples/cdn/convert_image/fixtures/extension-not-in-list.test.json new file mode 100644 index 0000000..5302e65 --- /dev/null +++ b/examples/cdn/convert_image/fixtures/extension-not-in-list.test.json @@ -0,0 +1,31 @@ +{ + "appType": "proxy-wasm", + "description": "Extension gif is not in FORMATS_TO_TRANSFORM list (jpg,png) — app does not schedule conversion", + "request": { + "method": "GET", + "url": "http://fastedge-builtin.debug/animation.gif", + "headers": { + "host": "example.com", + "User-Agent": "Mozilla/5.0 TestBrowser" + }, + "body": "" + }, + "response": { + "headers": { + "content-type": "image/gif" + }, + "body": "" + }, + "properties": { + "request.extension": "gif" + }, + "dotenv": { + "enabled": true, + "path": "." + }, + "logLevel": 2, + "wasm": { + "path": "/.fastedge-debug/app.wasm", + "description": "Default debugger WASM binary" + } +} diff --git a/examples/cdn/convert_image/fixtures/formats-not-set.live.json b/examples/cdn/convert_image/fixtures/formats-not-set.live.json new file mode 100644 index 0000000..cc62a54 --- /dev/null +++ b/examples/cdn/convert_image/fixtures/formats-not-set.live.json @@ -0,0 +1,3 @@ +{ + "_skip_live": "FORMATS_TO_TRANSFORM is set on the deployed app — this scenario requires a separate deployment with no env vars" +} diff --git a/examples/cdn/convert_image/fixtures/formats-not-set.test.json b/examples/cdn/convert_image/fixtures/formats-not-set.test.json new file mode 100644 index 0000000..122e6aa --- /dev/null +++ b/examples/cdn/convert_image/fixtures/formats-not-set.test.json @@ -0,0 +1,27 @@ +{ + "appType": "proxy-wasm", + "description": "Extension jpg present but FORMATS_TO_TRANSFORM env var not set — app does not schedule conversion", + "request": { + "method": "GET", + "url": "http://fastedge-builtin.debug/photo.jpg", + "headers": { + "host": "example.com", + "User-Agent": "Mozilla/5.0 TestBrowser" + }, + "body": "" + }, + "response": { + "headers": { + "content-type": "image/jpeg" + }, + "body": "" + }, + "properties": { + "request.extension": "jpg" + }, + "logLevel": 2, + "wasm": { + "path": "/.fastedge-debug/app.wasm", + "description": "Default debugger WASM binary" + } +} diff --git a/examples/cdn/convert_image/fixtures/no-extension.live.json b/examples/cdn/convert_image/fixtures/no-extension.live.json new file mode 100644 index 0000000..a654196 --- /dev/null +++ b/examples/cdn/convert_image/fixtures/no-extension.live.json @@ -0,0 +1,5 @@ +{ + "expected": { + "logs": ["No extension in request path, not transforming"] + } +} diff --git a/examples/cdn/convert_image/fixtures/no-extension.test.json b/examples/cdn/convert_image/fixtures/no-extension.test.json new file mode 100644 index 0000000..846ab89 --- /dev/null +++ b/examples/cdn/convert_image/fixtures/no-extension.test.json @@ -0,0 +1,27 @@ +{ + "appType": "proxy-wasm", + "description": "Request path has no file extension — app sets Image-Format: original and does not schedule conversion", + "request": { + "method": "GET", + "url": "http://fastedge-builtin.debug/api/data", + "headers": { + "host": "example.com", + "User-Agent": "Mozilla/5.0 TestBrowser" + }, + "body": "" + }, + "response": { + "headers": { + "content-type": "application/json" + }, + "body": "{\"ok\":true}" + }, + "properties": { + "request.extension": "" + }, + "logLevel": 2, + "wasm": { + "path": "/.fastedge-debug/app.wasm", + "description": "Default debugger WASM binary" + } +} diff --git a/examples/cdn/convert_image/fixtures/ua-in-ignore-list.live.json b/examples/cdn/convert_image/fixtures/ua-in-ignore-list.live.json new file mode 100644 index 0000000..3ae1e73 --- /dev/null +++ b/examples/cdn/convert_image/fixtures/ua-in-ignore-list.live.json @@ -0,0 +1,5 @@ +{ + "expected": { + "logs": ["User-Agent is in ignore list, not transforming"] + } +} diff --git a/examples/cdn/convert_image/fixtures/ua-in-ignore-list.test.json b/examples/cdn/convert_image/fixtures/ua-in-ignore-list.test.json new file mode 100644 index 0000000..d1ee1fb --- /dev/null +++ b/examples/cdn/convert_image/fixtures/ua-in-ignore-list.test.json @@ -0,0 +1,31 @@ +{ + "appType": "proxy-wasm", + "description": "User-Agent matches IGNORED_UA_LIST substring — app does not schedule conversion", + "request": { + "method": "GET", + "url": "http://fastedge-builtin.debug/photo.jpg", + "headers": { + "host": "example.com", + "User-Agent": "Googlebot/2.1 (+http://www.google.com/bot.html)" + }, + "body": "" + }, + "response": { + "headers": { + "content-type": "image/jpeg" + }, + "body": "" + }, + "properties": { + "request.extension": "jpg" + }, + "dotenv": { + "enabled": true, + "path": "." + }, + "logLevel": 2, + "wasm": { + "path": "/.fastedge-debug/app.wasm", + "description": "Default debugger WASM binary" + } +} diff --git a/examples/cdn/convert_image/src/lib.rs b/examples/cdn/convert_image/src/lib.rs index e79bafa..2909c20 100644 --- a/examples/cdn/convert_image/src/lib.rs +++ b/examples/cdn/convert_image/src/lib.rs @@ -1,39 +1,42 @@ +use image::*; use proxy_wasm::traits::*; use proxy_wasm::types::*; -use image::*; use std::{env, env::VarError, io::Cursor, str::from_utf8}; proxy_wasm::main! {{ proxy_wasm::set_log_level(LogLevel::Trace); - proxy_wasm::set_root_context(|_| -> Box { Box::new(HttpBodyRoot) }); + proxy_wasm::set_root_context(|_| -> Box { Box::new(ConvertImageRoot) }); }} -struct HttpBodyRoot; +struct ConvertImageRoot; -impl Context for HttpBodyRoot {} +impl Context for ConvertImageRoot {} -impl RootContext for HttpBodyRoot { +impl RootContext for ConvertImageRoot { fn get_type(&self) -> Option { Some(ContextType::HttpContext) } fn create_http_context(&self, _: u32) -> Option> { - Some(Box::new(HttpBody)) + Some(Box::new(ConvertImageContext)) } } -struct HttpBody; +struct ConvertImageContext; -impl Context for HttpBody {} +impl Context for ConvertImageContext {} -impl HttpContext for HttpBody { - fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action - { +impl HttpContext for ConvertImageContext { + fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action { // this header is used to select correct image version from cache self.add_http_request_header("Image-Format", "original"); // get extension - let Some(ext)= self.get_property(vec!["request.extension"]) else { + let path = self.get_property(vec!["request.path"]).map(|v| String::from_utf8(v).unwrap_or_default()).unwrap_or_default(); + println!("request.path={path:?}"); + let raw_ext = self.get_property(vec!["request.extension"]); + println!("request.extension={raw_ext:?}"); + let Some(ext) = raw_ext else { println!("No extension in request path, not transforming"); return Action::Continue; }; @@ -53,7 +56,10 @@ impl HttpContext for HttpBody { return Action::Continue; }; if !image_list.split(',').any(|entry| entry == ext) { - println!("extension {} is not in the list of formats to transform: {}, not transforming", ext, image_list); + println!( + "extension {} is not in the list of formats to transform: {}, not transforming", + ext, image_list + ); return Action::Continue; } @@ -62,6 +68,10 @@ impl HttpContext for HttpBody { println!("User-Agent header is not set, not transforming"); return Action::Continue; }; + if ua.is_empty() { + println!("User-Agent header is not set, not transforming"); + return Action::Continue; + } if let Ok(ua_to_ignore) = str_param("IGNORED_UA_LIST") { if ua_to_ignore.split(",").any(|entry| ua.contains(entry)) { println!("User-Agent is in ignore list, not transforming"); @@ -75,12 +85,14 @@ impl HttpContext for HttpBody { Action::Continue } - fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action - { + fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action { // only process 200 responses if let Some(status) = self.rsp_status() { if status != 200 { - println!("Response status is {} instead of expected 200, not transforming", status); + println!( + "Response status is {} instead of expected 200, not transforming", + status + ); return Action::Continue; } } else { @@ -110,13 +122,13 @@ impl HttpContext for HttpBody { Action::Continue } - fn on_http_response_body(&mut self, body_size: usize, end_of_stream: bool) -> Action - { - if !end_of_stream { // wait till we get complete body + fn on_http_response_body(&mut self, body_size: usize, end_of_stream: bool) -> Action { + if !end_of_stream { + // wait till we get complete body return Action::Pause; } - let Some(content_type)= self.get_property(vec!["response.content-type"]) else { + let Some(content_type) = self.get_property(vec!["response.content-type"]) else { return Action::Continue; }; @@ -129,7 +141,10 @@ impl HttpContext for HttpBody { if content_type != "image/avif" { // should never happen - println!("Content-Type {} is not supported, not transforming", content_type); + println!( + "Content-Type {} is not supported, not transforming", + content_type + ); return Action::Continue; } @@ -139,25 +154,29 @@ impl HttpContext for HttpBody { Ok(i) => i, Err(e) => { println!("cannot load image to memory {}, not converting", e); - return Action::Continue + return Action::Continue; } }; let mut out = Vec::new(); let mut c = Cursor::new(&mut out); - let res = img.write_with_encoder( - codecs::avif::AvifEncoder::new_with_speed_quality( - &mut c, - u8_param("AVIF_SPEED", 1, 10, 5), - u8_param("AVIF_QUALITY", 1, 100, 70)) - ); + let res = img.write_with_encoder(codecs::avif::AvifEncoder::new_with_speed_quality( + &mut c, + u8_param("AVIF_SPEED", 1, 10, 5), + u8_param("AVIF_QUALITY", 1, 100, 70), + )); match res { Ok(_) => { - println!("{} bytes -> {} bytes {}", body_size, out.len(), content_type); + println!( + "{} bytes -> {} bytes {}", + body_size, + out.len(), + content_type + ); self.set_http_response_body(0, body_size, &out) } - Err(e) => println!("cannot store transformed image {}", e) + Err(e) => println!("cannot store transformed image {}", e), } } else { println!("No response body to transform"); @@ -167,9 +186,9 @@ impl HttpContext for HttpBody { } } -impl HttpBody { +impl ConvertImageContext { fn rsp_status(&mut self) -> Option { - if let Some(status)= self.get_property(vec!["response.status"]) { + if let Some(status) = self.get_property(vec!["response.status"]) { if status.len() != 2 { println!("HTTP status property is not 2 bytes"); return None; @@ -180,8 +199,7 @@ impl HttpBody { } } -fn str_param(name: &str) -> Result -{ +fn str_param(name: &str) -> Result { let val = env::var(name)?; if val.is_empty() { return Err(VarError::NotPresent); @@ -190,8 +208,7 @@ fn str_param(name: &str) -> Result Ok(val) } -fn u8_param(name: &str, min: u8, max: u8, default: u8) -> u8 -{ +fn u8_param(name: &str, min: u8, max: u8, default: u8) -> u8 { let Ok(val) = env::var(name) else { println!("Param {} is not set, using default value {}", name, default); return default; @@ -203,17 +220,26 @@ fn u8_param(name: &str, min: u8, max: u8, default: u8) -> u8 let val = match val.parse() { Err(_) => { - println!("Param {} is not a valid number, using default value {}", name, default); + println!( + "Param {} is not a valid number, using default value {}", + name, default + ); return default; } Ok(v) => v, }; if val < min { - println!("Param {} is below minimum {}, using default value {}", name, min, default); + println!( + "Param {} is below minimum {}, using default value {}", + name, min, default + ); return default; } if val > max { - println!("Param {} is above maximum {}, using default value {}", name, max, default); + println!( + "Param {} is above maximum {}, using default value {}", + name, max, default + ); return default; } diff --git a/examples/cdn/cors/fixtures/disallowed-origin.live.json b/examples/cdn/cors/fixtures/disallowed-origin.live.json new file mode 100644 index 0000000..60dc3f7 --- /dev/null +++ b/examples/cdn/cors/fixtures/disallowed-origin.live.json @@ -0,0 +1,5 @@ +{ + "expected": { + "status": 200 + } +} diff --git a/examples/cdn/cors/fixtures/happy-path.live.json b/examples/cdn/cors/fixtures/happy-path.live.json new file mode 100644 index 0000000..8019980 --- /dev/null +++ b/examples/cdn/cors/fixtures/happy-path.live.json @@ -0,0 +1,10 @@ +{ + "expected": { + "status": 200, + "headers": { + "access-control-allow-origin": "https://app.example.com", + "vary": "Origin", + "access-control-expose-headers": "X-Request-Id, X-RateLimit-Remaining" + } + } +} diff --git a/examples/cdn/cors/fixtures/no-origin.live.json b/examples/cdn/cors/fixtures/no-origin.live.json new file mode 100644 index 0000000..60dc3f7 --- /dev/null +++ b/examples/cdn/cors/fixtures/no-origin.live.json @@ -0,0 +1,5 @@ +{ + "expected": { + "status": 200 + } +} diff --git a/examples/cdn/cors/fixtures/preflight.live.json b/examples/cdn/cors/fixtures/preflight.live.json new file mode 100644 index 0000000..d287ea0 --- /dev/null +++ b/examples/cdn/cors/fixtures/preflight.live.json @@ -0,0 +1,12 @@ +{ + "expected": { + "status": 204, + "headers": { + "access-control-allow-origin": "https://app.example.com", + "access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS", + "access-control-allow-headers": "Content-Type, Authorization, X-Custom-Header", + "access-control-max-age": "86400", + "vary": "Origin" + } + } +} diff --git a/examples/cdn/cors/fixtures/wildcard-origins.live.json b/examples/cdn/cors/fixtures/wildcard-origins.live.json new file mode 100644 index 0000000..63f56eb --- /dev/null +++ b/examples/cdn/cors/fixtures/wildcard-origins.live.json @@ -0,0 +1,8 @@ +{ + "expected": { + "status": 200, + "headers": { + "access-control-allow-origin": "*" + } + } +} diff --git a/examples/cdn/cors/fixtures/wildcard-origins.test.json b/examples/cdn/cors/fixtures/wildcard-origins.test.json index e0044e8..6eb7caf 100644 --- a/examples/cdn/cors/fixtures/wildcard-origins.test.json +++ b/examples/cdn/cors/fixtures/wildcard-origins.test.json @@ -18,7 +18,7 @@ }, "dotenv": { "enabled": true, - "path": "fixtures/wildcard.env" + "path": "wildcard" }, "logLevel": 2, "wasm": { diff --git a/examples/cdn/cors/fixtures/wildcard/.env b/examples/cdn/cors/fixtures/wildcard/.env new file mode 100644 index 0000000..650ee7e --- /dev/null +++ b/examples/cdn/cors/fixtures/wildcard/.env @@ -0,0 +1,3 @@ +FASTEDGE_VAR_ENV_ALLOWED_ORIGINS=* +FASTEDGE_VAR_ENV_ALLOWED_METHODS=GET, POST, OPTIONS +FASTEDGE_VAR_ENV_MAX_AGE=3600 diff --git a/examples/cdn/custom/fixtures/invalid-status.live.json b/examples/cdn/custom/fixtures/invalid-status.live.json new file mode 100644 index 0000000..824b613 --- /dev/null +++ b/examples/cdn/custom/fixtures/invalid-status.live.json @@ -0,0 +1,6 @@ +{ + "expected": { + "status": 400, + "body": "Malformed request - invalid status code" + } +} diff --git a/examples/cdn/custom/fixtures/status-200.live.json b/examples/cdn/custom/fixtures/status-200.live.json new file mode 100644 index 0000000..60dc3f7 --- /dev/null +++ b/examples/cdn/custom/fixtures/status-200.live.json @@ -0,0 +1,5 @@ +{ + "expected": { + "status": 200 + } +} diff --git a/examples/cdn/custom/fixtures/status-403.live.json b/examples/cdn/custom/fixtures/status-403.live.json new file mode 100644 index 0000000..4b41c29 --- /dev/null +++ b/examples/cdn/custom/fixtures/status-403.live.json @@ -0,0 +1,5 @@ +{ + "expected": { + "status": 403 + } +} diff --git a/examples/cdn/custom_error_pages/fixtures/200-passthrough.live.json b/examples/cdn/custom_error_pages/fixtures/200-passthrough.live.json new file mode 100644 index 0000000..60dc3f7 --- /dev/null +++ b/examples/cdn/custom_error_pages/fixtures/200-passthrough.live.json @@ -0,0 +1,5 @@ +{ + "expected": { + "status": 200 + } +} diff --git a/examples/cdn/custom_error_pages/fixtures/400-bad-request.live.json b/examples/cdn/custom_error_pages/fixtures/400-bad-request.live.json new file mode 100644 index 0000000..0fa2f62 --- /dev/null +++ b/examples/cdn/custom_error_pages/fixtures/400-bad-request.live.json @@ -0,0 +1,10 @@ +{ + "expected": { + "status": 400, + "headers": { + "content-type": "text/html", + "transfer-encoding": "Chunked" + }, + "bodyContains": [" Box { Box::new(HttpHeadersRoot) }); + proxy_wasm::set_root_context(|_| -> Box { Box::new(GeoblockRoot) }); }} -struct HttpHeadersRoot; +struct GeoblockRoot; -impl Context for HttpHeadersRoot {} +impl Context for GeoblockRoot {} -impl RootContext for HttpHeadersRoot { +impl RootContext for GeoblockRoot { fn create_http_context(&self, _context_id: u32) -> Option> { - Some(Box::new(HttpHeaders {})) + Some(Box::new(GeoblockContext {})) } fn get_type(&self) -> Option { @@ -23,15 +23,15 @@ impl RootContext for HttpHeadersRoot { } } -struct HttpHeaders {} +struct GeoblockContext {} -impl Context for HttpHeaders {} +impl Context for GeoblockContext {} const BAD_GATEWAY: u32 = 502; const FORBIDDEN: u32 = 403; const INTERNAL_SERVER_ERROR: u32 = 500; -impl HttpContext for HttpHeaders { +impl HttpContext for GeoblockContext { fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action { let Ok(blacklist) = env::var("BLACKLIST") else { self.send_http_response(INTERNAL_SERVER_ERROR, vec![], Some(b"App misconfigured")); @@ -69,7 +69,7 @@ impl HttpContext for HttpHeaders { .unwrap() .as_secs(); - if now > tw_start || now <= tw_end { + if now >= tw_start && now <= tw_end { self.send_http_response(FORBIDDEN, vec![], Some(b"Request blacklisted")); return Action::Pause; } diff --git a/examples/cdn/headers/fixtures/happy-path.live.json b/examples/cdn/headers/fixtures/happy-path.live.json new file mode 100644 index 0000000..e5ef7d6 --- /dev/null +++ b/examples/cdn/headers/fixtures/happy-path.live.json @@ -0,0 +1,12 @@ +{ + "expected": { + "status": 200, + "headers": { + "new-response-header": "value-02" + }, + "logs": [ + "-> host: example.com", + "-> new-response-header: value-02" + ] + } +} diff --git a/examples/cdn/headers/src/lib.rs b/examples/cdn/headers/src/lib.rs index f28c47f..ae3cc75 100644 --- a/examples/cdn/headers/src/lib.rs +++ b/examples/cdn/headers/src/lib.rs @@ -155,7 +155,7 @@ impl HttpContext for HttpHeaders { let diff_bytes = diff_bytes.difference(&expected_bytes).collect::>(); if !diff_bytes.is_empty() { - println!("different headers bytes: {:?}", diff); + println!("different headers bytes: {:?}", diff_bytes); self.send_http_response(552, vec![], None); return Action::Pause; } @@ -307,16 +307,17 @@ impl HttpContext for HttpHeaders { .difference(&original_headers_bytes) .collect::>(); - if expected != diff { - let diff = diff.difference(&expected).collect::>(); + let diff = diff.difference(&expected).collect::>(); + + if !diff.is_empty() { println!("different headers: {:?}", diff); self.send_http_response(552, vec![], None); return Action::Pause; } - if expected_bytes != diff_bytes { - let diff = diff_bytes.difference(&expected_bytes).collect::>(); - println!("different headers bytes: {:?}", diff); + let diff_bytes = diff_bytes.difference(&expected_bytes).collect::>(); + if !diff_bytes.is_empty() { + println!("different headers bytes: {:?}", diff_bytes); self.send_http_response(552, vec![], None); return Action::Pause; } @@ -347,8 +348,4 @@ impl HttpContext for HttpHeaders { Action::Continue } - - fn on_log(&mut self) { - println!("#{} completed.", self.context_id); - } } diff --git a/examples/cdn/hello_world/README.md b/examples/cdn/hello_world/README.md index 12d09d2..553d143 100644 --- a/examples/cdn/hello_world/README.md +++ b/examples/cdn/hello_world/README.md @@ -3,3 +3,33 @@ # Hello World (CDN) Minimal CDN app demonstrating the proxy-wasm lifecycle. Logs a message at each request/response phase and adds an `x-powered-by: FastEdge` response header. Use as a starting point for new CDN apps. + +## What it does + +Implements all four proxy-wasm HTTP lifecycle hooks. Each hook logs a message and returns `Continue` so every request passes through unchanged: + +| Hook | Phase | +|---|---| +| `on_http_request_headers` | Incoming request headers | +| `on_http_request_body` | Incoming request body | +| `on_http_response_headers` | Origin response headers — also adds `x-powered-by: FastEdge` | +| `on_http_response_body` | Origin response body | + +## Build + +```sh +cargo build --release +# Output: target/wasm32-wasip1/release/hello_world.wasm +``` + +## Expected output + +Logs (in order): +``` +Hello from on_http_request_headers +Hello from on_http_request_body +Hello from on_http_response_headers +Hello from on_http_response_body +``` + +Response header added: `x-powered-by: FastEdge` diff --git a/examples/cdn/hello_world/fixtures/happy-path.live.json b/examples/cdn/hello_world/fixtures/happy-path.live.json new file mode 100644 index 0000000..5db37d4 --- /dev/null +++ b/examples/cdn/hello_world/fixtures/happy-path.live.json @@ -0,0 +1,14 @@ +{ + "expected": { + "logs": [ + "Hello from on_http_request_headers", + "Hello from on_http_request_body", + "Hello from on_http_response_headers", + "Hello from on_http_response_body" + ], + "headers": { + "x-powered-by": "FastEdge" + }, + "status": 200 + } +} diff --git a/examples/cdn/http_call/fixtures/happy-path.live.json b/examples/cdn/http_call/fixtures/happy-path.live.json new file mode 100644 index 0000000..38ad1e0 --- /dev/null +++ b/examples/cdn/http_call/fixtures/happy-path.live.json @@ -0,0 +1,9 @@ +{ + "expected": { + "logs": [ + "state: 0", + "Dispatched http call with token id:", + "Received http call response with token id:" + ] + } +} diff --git a/examples/cdn/http_call/src/lib.rs b/examples/cdn/http_call/src/lib.rs index bf7ef6c..bed56c5 100644 --- a/examples/cdn/http_call/src/lib.rs +++ b/examples/cdn/http_call/src/lib.rs @@ -30,7 +30,7 @@ impl Context for HttpHeaders { &mut self, token_id: u32, num_headers: usize, - body_size: usize, + _body_size: usize, _num_trailers: usize, ) { println!( @@ -38,26 +38,17 @@ impl Context for HttpHeaders { ); //If num_headers is 0, then the HTTP call failed. if num_headers != 0 { - let user_agent = self.get_http_call_response_header("user-agent"); - println!("User-Agent: {:?}", user_agent); - let headers = self.get_http_call_response_headers(); - println!("Response headers:"); - for (name, value) in &headers { - println!(" {}: {}", name, value); - } - let headers_value = self.get_http_call_response_headers_bytes(); - for (name, value) in &headers_value { - println!(" {}: {:?}", name, value); - } - - let body = self.get_http_call_response_body(0, body_size); - println!("Response body: {:?}", body); + let headers_str = headers + .iter() + .map(|(name, value)| format!("\"{}: {}\"", name, value)) + .collect::>() + .join(","); + println!("Response headers: [{}]", headers_str); self.state = 1; // Set state to 1 to indicate that the HTTP call response was received successfully. self.resume_http_request(); - // or self.resume_http_response() } else { self.reset_http_request(); } diff --git a/examples/cdn/jwt/Cargo.lock b/examples/cdn/jwt/Cargo.lock new file mode 100644 index 0000000..dc0777c --- /dev/null +++ b/examples/cdn/jwt/Cargo.lock @@ -0,0 +1,983 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fastedge" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9042fccaffdd2171e8ff481e5c21d22f15b8a4454b37273c2f3f902fb3d375e" +dependencies = [ + "bytes", + "fastedge-derive", + "http", + "mime", + "thiserror", + "wit-bindgen", +] + +[[package]] +name = "fastedge-derive" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acacf180f92cbdf6f2fe1a772b7d92301e430ba207fb15b2fc87ccab4e418ff9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "headers" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +dependencies = [ + "base64", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "jwt" +version = "0.1.0" +dependencies = [ + "fastedge", + "headers", + "jsonwebtoken", + "proxy-wasm", + "serde", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proxy-wasm" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8d35d9e2bc5104e2e954b149aa1d5f9fa3bb27f73b45b2706020fed101db685" +dependencies = [ + "hashbrown 0.16.1", + "log", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "untrusted", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "bitflags", + "futures", + "once_cell", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/examples/cdn/jwt/fixtures/.env b/examples/cdn/jwt/fixtures/.env new file mode 100644 index 0000000..ae14290 --- /dev/null +++ b/examples/cdn/jwt/fixtures/.env @@ -0,0 +1,2 @@ +FASTEDGE_VAR_SECRET_SECRET=a-string-secret-at-least-256-bits-long-thats-hard-to-break + diff --git a/examples/cdn/jwt/fixtures/expired-token.live.json b/examples/cdn/jwt/fixtures/expired-token.live.json new file mode 100644 index 0000000..6693e69 --- /dev/null +++ b/examples/cdn/jwt/fixtures/expired-token.live.json @@ -0,0 +1,7 @@ +{ + "expected": { + "logs": ["Token expired"], + "status": 403, + "body": "Token expired" + } +} diff --git a/examples/cdn/jwt/fixtures/expired-token.test.json b/examples/cdn/jwt/fixtures/expired-token.test.json new file mode 100644 index 0000000..fb5260c --- /dev/null +++ b/examples/cdn/jwt/fixtures/expired-token.test.json @@ -0,0 +1,22 @@ +{ + "appType": "proxy-wasm", + "description": "Expired JWT (exp in 2020) — returns 403", + "request": { + "method": "GET", + "url": "http://fastedge-builtin.debug", + "headers": { + "host": "example.com", + "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0LXVzZXIiLCJleHAiOjE1Nzc4MzY4MDB9.4jxrYjcCcbcMrifZ4qzxtMzrHiNw9sHzvfW0dWkgj2w" + }, + "body": "" + }, + "dotenv": { + "enabled": true, + "path": "." + }, + "logLevel": 2, + "wasm": { + "path": "/.fastedge-debug/app.wasm", + "description": "Default debugger WASM binary" + } +} diff --git a/examples/cdn/jwt/fixtures/invalid-token.live.json b/examples/cdn/jwt/fixtures/invalid-token.live.json new file mode 100644 index 0000000..5dda304 --- /dev/null +++ b/examples/cdn/jwt/fixtures/invalid-token.live.json @@ -0,0 +1,7 @@ +{ + "expected": { + "logs": ["Token is invalid"], + "status": 403, + "bodyContains": "Could not decode token" + } +} diff --git a/examples/cdn/jwt/fixtures/invalid-token.test.json b/examples/cdn/jwt/fixtures/invalid-token.test.json new file mode 100644 index 0000000..8aea886 --- /dev/null +++ b/examples/cdn/jwt/fixtures/invalid-token.test.json @@ -0,0 +1,22 @@ +{ + "appType": "proxy-wasm", + "description": "JWT with wrong signature — returns 403 token invalid", + "request": { + "method": "GET", + "url": "http://fastedge-builtin.debug", + "headers": { + "host": "example.com", + "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0LXVzZXIiLCJleHAiOjQxMDI0NDQ4MDB9.INVALIDSIGNATUREXXXXXXXXXXXXXXXXXXXXXXX" + }, + "body": "" + }, + "dotenv": { + "enabled": true, + "path": "." + }, + "logLevel": 2, + "wasm": { + "path": "/.fastedge-debug/app.wasm", + "description": "Default debugger WASM binary" + } +} diff --git a/examples/cdn/jwt/fixtures/missing-auth-header.live.json b/examples/cdn/jwt/fixtures/missing-auth-header.live.json new file mode 100644 index 0000000..eae3f6e --- /dev/null +++ b/examples/cdn/jwt/fixtures/missing-auth-header.live.json @@ -0,0 +1,7 @@ +{ + "expected": { + "logs": ["No auth header"], + "status": 401, + "body": "No Authorization header" + } +} diff --git a/examples/cdn/jwt/fixtures/missing-auth-header.test.json b/examples/cdn/jwt/fixtures/missing-auth-header.test.json new file mode 100644 index 0000000..e89eea5 --- /dev/null +++ b/examples/cdn/jwt/fixtures/missing-auth-header.test.json @@ -0,0 +1,21 @@ +{ + "appType": "proxy-wasm", + "description": "No Authorization header — returns 401", + "request": { + "method": "GET", + "url": "http://fastedge-builtin.debug", + "headers": { + "host": "example.com" + }, + "body": "" + }, + "dotenv": { + "enabled": true, + "path": "." + }, + "logLevel": 2, + "wasm": { + "path": "/.fastedge-debug/app.wasm", + "description": "Default debugger WASM binary" + } +} diff --git a/examples/cdn/jwt/fixtures/missing-secret.live.json b/examples/cdn/jwt/fixtures/missing-secret.live.json new file mode 100644 index 0000000..8c75fbc --- /dev/null +++ b/examples/cdn/jwt/fixtures/missing-secret.live.json @@ -0,0 +1,7 @@ +{ + "expected": { + "logs": ["'secret' param not set"], + "status": 500, + "body": "App misconfigured" + } +} diff --git a/examples/cdn/jwt/fixtures/missing-secret.test.json b/examples/cdn/jwt/fixtures/missing-secret.test.json new file mode 100644 index 0000000..bb63e39 --- /dev/null +++ b/examples/cdn/jwt/fixtures/missing-secret.test.json @@ -0,0 +1,18 @@ +{ + "appType": "proxy-wasm", + "description": "Secret not configured — returns 500 app misconfigured", + "request": { + "method": "GET", + "url": "http://fastedge-builtin.debug", + "headers": { + "host": "example.com", + "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0LXVzZXIiLCJleHAiOjQxMDI0NDQ4MDB9.hS9YgaBPC0uo7LQi1_ai0FdI_vu20wU9wG1-PG6-CG0" + }, + "body": "" + }, + "logLevel": 2, + "wasm": { + "path": "/.fastedge-debug/app.wasm", + "description": "Default debugger WASM binary" + } +} diff --git a/examples/cdn/jwt/fixtures/valid-token.live.json b/examples/cdn/jwt/fixtures/valid-token.live.json new file mode 100644 index 0000000..3ddba8a --- /dev/null +++ b/examples/cdn/jwt/fixtures/valid-token.live.json @@ -0,0 +1,5 @@ +{ + "expected": { + "logs": ["Token ok"] + } +} diff --git a/examples/cdn/jwt/fixtures/valid-token.test.json b/examples/cdn/jwt/fixtures/valid-token.test.json new file mode 100644 index 0000000..fdc7b82 --- /dev/null +++ b/examples/cdn/jwt/fixtures/valid-token.test.json @@ -0,0 +1,28 @@ +{ + "appType": "proxy-wasm", + "description": "Valid HS256 JWT with far-future exp — request passes through", + "request": { + "method": "GET", + "url": "http://fastedge-builtin.debug", + "headers": { + "host": "example.com", + "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0LXVzZXIiLCJleHAiOjQxMDI0NDQ4MDB9.h4nrvZCtvX1OtRJMjID961FJGw2kkRM0zySLXN7VEtg" + }, + "body": "" + }, + "response": { + "headers": { + "content-type": "text/plain" + }, + "body": "OK" + }, + "dotenv": { + "enabled": true, + "path": "." + }, + "logLevel": 2, + "wasm": { + "path": "/.fastedge-debug/app.wasm", + "description": "Default debugger WASM binary" + } +} diff --git a/examples/cdn/key_value/Cargo.lock b/examples/cdn/key_value/Cargo.lock new file mode 100644 index 0000000..2cb4935 --- /dev/null +++ b/examples/cdn/key_value/Cargo.lock @@ -0,0 +1,542 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fastedge" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9042fccaffdd2171e8ff481e5c21d22f15b8a4454b37273c2f3f902fb3d375e" +dependencies = [ + "bytes", + "fastedge-derive", + "http", + "mime", + "thiserror", + "wit-bindgen", +] + +[[package]] +name = "fastedge-derive" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acacf180f92cbdf6f2fe1a772b7d92301e430ba207fb15b2fc87ccab4e418ff9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "key_value" +version = "0.1.0" +dependencies = [ + "fastedge", + "proxy-wasm", + "querystring", + "serde_json", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proxy-wasm" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8d35d9e2bc5104e2e954b149aa1d5f9fa3bb27f73b45b2706020fed101db685" +dependencies = [ + "hashbrown 0.16.1", + "log", +] + +[[package]] +name = "querystring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9318ead08c799aad12a55a3e78b82e0b6167271ffd1f627b758891282f739187" + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "bitflags", + "futures", + "once_cell", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/examples/cdn/key_value/fixtures/kv-get.live.json b/examples/cdn/key_value/fixtures/kv-get.live.json new file mode 100644 index 0000000..7f3f552 --- /dev/null +++ b/examples/cdn/key_value/fixtures/kv-get.live.json @@ -0,0 +1,8 @@ +{ + "expected": { + "bodyContains": "mystore", + "headers": { + "content-type": "application/json" + } + } +} diff --git a/examples/cdn/key_value/fixtures/kv-get.test.json b/examples/cdn/key_value/fixtures/kv-get.test.json new file mode 100644 index 0000000..c485403 --- /dev/null +++ b/examples/cdn/key_value/fixtures/kv-get.test.json @@ -0,0 +1,26 @@ +{ + "appType": "proxy-wasm", + "description": "KV get operation — calls Store::open host function (runner gap expected)", + "request": { + "method": "GET", + "url": "http://fastedge-builtin.debug", + "headers": { + "host": "example.com" + }, + "body": "" + }, + "properties": { + "request.query": "store=mystore&action=get&key=mykey" + }, + "response": { + "headers": { + "content-type": "text/plain" + }, + "body": "upstream response" + }, + "logLevel": 2, + "wasm": { + "path": "/.fastedge-debug/app.wasm", + "description": "Default debugger WASM binary" + } +} diff --git a/examples/cdn/key_value/fixtures/missing-store-param.live.json b/examples/cdn/key_value/fixtures/missing-store-param.live.json new file mode 100644 index 0000000..3b27b7c --- /dev/null +++ b/examples/cdn/key_value/fixtures/missing-store-param.live.json @@ -0,0 +1,8 @@ +{ + "expected": { + "bodyContains": "Missing required param 'store'", + "headers": { + "content-type": "application/json" + } + } +} diff --git a/examples/cdn/key_value/fixtures/missing-store-param.test.json b/examples/cdn/key_value/fixtures/missing-store-param.test.json new file mode 100644 index 0000000..c3708a9 --- /dev/null +++ b/examples/cdn/key_value/fixtures/missing-store-param.test.json @@ -0,0 +1,26 @@ +{ + "appType": "proxy-wasm", + "description": "Query params present but 'store' param missing — returns error", + "request": { + "method": "GET", + "url": "http://fastedge-builtin.debug", + "headers": { + "host": "example.com" + }, + "body": "" + }, + "properties": { + "request.query": "action=get&key=mykey" + }, + "response": { + "headers": { + "content-type": "text/plain" + }, + "body": "upstream response" + }, + "logLevel": 2, + "wasm": { + "path": "/.fastedge-debug/app.wasm", + "description": "Default debugger WASM binary" + } +} diff --git a/examples/cdn/key_value/fixtures/no-query-params.live.json b/examples/cdn/key_value/fixtures/no-query-params.live.json new file mode 100644 index 0000000..b47bbb8 --- /dev/null +++ b/examples/cdn/key_value/fixtures/no-query-params.live.json @@ -0,0 +1,8 @@ +{ + "expected": { + "bodyContains": "App must be called with query parameters", + "headers": { + "content-type": "application/json" + } + } +} diff --git a/examples/cdn/key_value/fixtures/no-query-params.test.json b/examples/cdn/key_value/fixtures/no-query-params.test.json new file mode 100644 index 0000000..0741519 --- /dev/null +++ b/examples/cdn/key_value/fixtures/no-query-params.test.json @@ -0,0 +1,23 @@ +{ + "appType": "proxy-wasm", + "description": "No query parameters — returns error about missing query params", + "request": { + "method": "GET", + "url": "http://fastedge-builtin.debug", + "headers": { + "host": "example.com" + }, + "body": "" + }, + "response": { + "headers": { + "content-type": "text/plain" + }, + "body": "upstream response" + }, + "logLevel": 2, + "wasm": { + "path": "/.fastedge-debug/app.wasm", + "description": "Default debugger WASM binary" + } +} diff --git a/examples/cdn/key_value/src/lib.rs b/examples/cdn/key_value/src/lib.rs index 5d2203a..520b8d6 100644 --- a/examples/cdn/key_value/src/lib.rs +++ b/examples/cdn/key_value/src/lib.rs @@ -223,7 +223,7 @@ impl KvStoreContext { } fn send_error(&self, msg: &str, body_size: usize) { - proxy_wasm::hostcalls::log(LogLevel::Error, msg).ok(); + println!("{}", msg); self.set_property( vec!["response", "status"], Some(b"500"), diff --git a/examples/cdn/large_env_variable/fixtures/happy-path.live.json b/examples/cdn/large_env_variable/fixtures/happy-path.live.json new file mode 100644 index 0000000..9458385 --- /dev/null +++ b/examples/cdn/large_env_variable/fixtures/happy-path.live.json @@ -0,0 +1,8 @@ +{ + "expected": { + "logs": [ + "LARGE_CONFIG size: 109 bytes" + ], + "status": 200 + } +} diff --git a/examples/cdn/large_env_variable/fixtures/missing-config.live.json b/examples/cdn/large_env_variable/fixtures/missing-config.live.json new file mode 100644 index 0000000..f3fb662 --- /dev/null +++ b/examples/cdn/large_env_variable/fixtures/missing-config.live.json @@ -0,0 +1,8 @@ +{ + "expected": { + "logs": [ + "LARGE_CONFIG size: 0 bytes" + ], + "status": 200 + } +} diff --git a/examples/cdn/large_env_variable/src/lib.rs b/examples/cdn/large_env_variable/src/lib.rs index e91d459..03bea9f 100644 --- a/examples/cdn/large_env_variable/src/lib.rs +++ b/examples/cdn/large_env_variable/src/lib.rs @@ -49,11 +49,7 @@ impl HttpContext for LargeEnvContext { let config = dictionary::get("LARGE_CONFIG").unwrap_or_default(); let size = config.len(); - proxy_wasm::hostcalls::log( - LogLevel::Info, - &format!("LARGE_CONFIG size: {} bytes", size), - ) - .ok(); + println!("LARGE_CONFIG size: {} bytes", size); self.add_http_request_header("x-config-size", &size.to_string()); diff --git a/examples/cdn/log_time/fixtures/happy-path.live.json b/examples/cdn/log_time/fixtures/happy-path.live.json new file mode 100644 index 0000000..f5166a5 --- /dev/null +++ b/examples/cdn/log_time/fixtures/happy-path.live.json @@ -0,0 +1,9 @@ +{ + "expected": { + "status": 200, + "logs": [ + "on_http_request_headers:", + "on_http_response_headers:" + ] + } +} diff --git a/examples/cdn/log_time/src/lib.rs b/examples/cdn/log_time/src/lib.rs index c36568a..57cb311 100644 --- a/examples/cdn/log_time/src/lib.rs +++ b/examples/cdn/log_time/src/lib.rs @@ -52,8 +52,4 @@ impl HttpContext for HttpHeaders { ); Action::Continue } - - fn on_log(&mut self) { - info!("#{} completed.", self.context_id); - } } diff --git a/examples/cdn/md2html/fixtures/convert-to-html.live.json b/examples/cdn/md2html/fixtures/convert-to-html.live.json new file mode 100644 index 0000000..3f651df --- /dev/null +++ b/examples/cdn/md2html/fixtures/convert-to-html.live.json @@ -0,0 +1,14 @@ +{ + "expected": { + "logs": [ + "On response headers", + "Response is markdown, converting to HTML", + "Converted" + ], + "headers": { + "content-type": "text/html" + }, + "status": 200, + "bodyContains": "" + } +} diff --git a/examples/cdn/md2html/fixtures/html-passthrough.live.json b/examples/cdn/md2html/fixtures/html-passthrough.live.json new file mode 100644 index 0000000..5e446ae --- /dev/null +++ b/examples/cdn/md2html/fixtures/html-passthrough.live.json @@ -0,0 +1,16 @@ +{ + "expected": { + "logs": [ + "On response headers" + ], + "noLogs": [ + "converting to HTML", + "Converted" + ], + "headers": { + "content-type": "text/html" + }, + "status": 200, + "body": "Already HTML" + } +} diff --git a/examples/cdn/properties/README.md b/examples/cdn/properties/README.md index 2f2bdbb..a7329b6 100644 --- a/examples/cdn/properties/README.md +++ b/examples/cdn/properties/README.md @@ -2,4 +2,53 @@ # Properties (CDN) -Extracts and manipulates request properties — URL, path, host, and geo data — using the proxy-wasm ABI. +Extracts and manipulates request properties — URL, path, host, and geo data — using the proxy-wasm ABI. Forwards each extracted value as a response header so downstream clients or logging pipelines can inspect them. + +## What it does + +On every request, reads the following properties and adds each as a response header: + +| Property key | Response header | +|---|---| +| `request.url` | `request-uri` | +| `request.host` | `request-host` | +| `request.path` | `request-path` | +| `request.scheme` | `request-scheme` | +| `request.extension` | `request-extension` | +| `request.query` | `request-query` | +| `request.x_real_ip` | `request-x-real-ip` | +| `request.country` | `request-country` | +| `request.city` | `request-city` | +| `request.asn` | `request-asn` | +| `request.geo.long` | `request-long` | +| `request.geo.lat` | `request-lat` | +| `request.country.name` | `request-country-names` | +| `request.region` | `request-country-region` | +| `request.continent` | `request-continent` | + +If any property is missing the handler sends a 55x error response and stops — each property has a unique status code to identify exactly which lookup failed. `request.extension` is the exception: because a URL path often has no file extension, a missing value defaults to an empty string instead of triggering an error. + +## Query-param overrides + +After extracting all properties, the handler checks for override query parameters and rewrites the corresponding upstream request property if present: + +| Query param | Overwrites | +|---|---| +| `?url=` | `request.url` | +| `?host=` | `request.host` | +| `?path=` | `request.path` | + +## nginx log field + +Sets `nginx.log_field1` to `"from_wasm nginx.log_field1"` on every request, demonstrating how to write custom values into the CDN access log. + +## Build + +```sh +cargo build --release +# Output: target/wasm32-wasip1/release/properties.wasm +``` + +## Notes + +`fixtures/force-server-properties.json` is a visual-debugger configuration file (no `.test.json` extension), not a test fixture. It is used to seed server-side properties when running the app in the FastEdge visual debugger. diff --git a/examples/cdn/properties/fixtures/happy-path.live.json b/examples/cdn/properties/fixtures/happy-path.live.json new file mode 100644 index 0000000..4116184 --- /dev/null +++ b/examples/cdn/properties/fixtures/happy-path.live.json @@ -0,0 +1,26 @@ +{ + "expected": { + "logs": [ + "uri = ", + "host = ", + "path = ", + "scheme = https", + "extension = ", + "query = ", + "client_ip = ", + "country = ", + "city = ", + "asn = ", + "long = ", + "lat = ", + "country names = ", + "region = ", + "continent = ", + "query=test=value" + ], + "headers": { + "request-scheme": "https", + "request-query": "test=value" + } + } +} diff --git a/examples/cdn/properties/src/lib.rs b/examples/cdn/properties/src/lib.rs index 405e199..6953c38 100644 --- a/examples/cdn/properties/src/lib.rs +++ b/examples/cdn/properties/src/lib.rs @@ -1,20 +1,18 @@ use log::info; use proxy_wasm::traits::*; use proxy_wasm::types::*; -use std::borrow::Cow; - proxy_wasm::main! {{ proxy_wasm::set_log_level(LogLevel::Trace); - proxy_wasm::set_root_context(|_| -> Box { Box::new(HttpHeadersRoot) }); + proxy_wasm::set_root_context(|_| -> Box { Box::new(PropertiesRoot) }); }} -struct HttpHeadersRoot; +struct PropertiesRoot; -impl Context for HttpHeadersRoot {} +impl Context for PropertiesRoot {} -impl RootContext for HttpHeadersRoot { +impl RootContext for PropertiesRoot { fn create_http_context(&self, context_id: u32) -> Option> { - Some(Box::new(HttpHeaders { context_id })) + Some(Box::new(PropertiesContext { context_id })) } fn get_type(&self) -> Option { @@ -22,11 +20,11 @@ impl RootContext for HttpHeadersRoot { } } -struct HttpHeaders { +struct PropertiesContext { context_id: u32, } -impl Context for HttpHeaders {} +impl Context for PropertiesContext {} pub const REQUEST_URI: &str = "request.url"; pub const REQUEST_HOST: &str = "request.host"; @@ -44,7 +42,7 @@ pub const REQUEST_REGION: &str = "request.region"; pub const REQUEST_CONTINENT: &str = "request.continent"; pub const REQUEST_COUNTRY_NAME: &str = "request.country.name"; -impl HttpContext for HttpHeaders { +impl HttpContext for PropertiesContext { fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action { let Some(uri) = self.get_property(vec![REQUEST_URI]) else { self.send_http_response(551, vec![], None); @@ -74,10 +72,7 @@ impl HttpContext for HttpHeaders { println!(" scheme = {} ", String::from_utf8_lossy(&scheme)); self.add_http_response_header_bytes("request-scheme", &scheme); - let Some(extension) = self.get_property(vec![REQUEST_EXTENSION]) else { - self.send_http_response(555, vec![], None); - return Action::Pause; - }; + let extension = self.get_property(vec![REQUEST_EXTENSION]).unwrap_or_default(); println!(" extension = {} ", String::from_utf8_lossy(&extension)); self.add_http_response_header_bytes("request-extension", &extension); @@ -110,7 +105,7 @@ impl HttpContext for HttpHeaders { self.add_http_response_header_bytes("request-city", &city); let Some(value) = self.get_property(vec![REQUEST_ASN]) else { - self.send_http_response(561, vec![], None); + self.send_http_response(560, vec![], None); return Action::Pause; }; println!(" asn = {} ", String::from_utf8_lossy(&value)); @@ -201,19 +196,3 @@ impl HttpContext for HttpHeaders { } } -pub fn deserialize_country_names(bytes: &[u8]) -> Vec> { - let mut path = Vec::new(); - if bytes.is_empty() { - return path; - } - let mut p = 0; - while p < bytes.len() { - let s = p; - while p < bytes.len() && bytes[p] != 0 { - p += 1; - } - path.push(String::from_utf8_lossy(&bytes[s..p])); - p += 1; - } - path -} diff --git a/examples/cdn/variables_and_secrets/fixtures/happy-path.live.json b/examples/cdn/variables_and_secrets/fixtures/happy-path.live.json new file mode 100644 index 0000000..2cf4613 --- /dev/null +++ b/examples/cdn/variables_and_secrets/fixtures/happy-path.live.json @@ -0,0 +1,8 @@ +{ + "expected": { + "logs": [ + "USERNAME: cdn-test-user", + "PASSWORD: cdn-test-secret" + ] + } +} diff --git a/examples/cdn/variables_and_secrets/src/lib.rs b/examples/cdn/variables_and_secrets/src/lib.rs index 9886e3f..16150b1 100644 --- a/examples/cdn/variables_and_secrets/src/lib.rs +++ b/examples/cdn/variables_and_secrets/src/lib.rs @@ -49,10 +49,16 @@ impl HttpContext for VariablesContext { .and_then(|v| String::from_utf8(v).ok()) .unwrap_or_default(); - proxy_wasm::hostcalls::log(LogLevel::Info, &format!("USERNAME: {}", username)).ok(); - proxy_wasm::hostcalls::log(LogLevel::Info, &format!("PASSWORD: {}", password)).ok(); + println!("USERNAME: {}", username); + // WARNING: Secrets are stored and retrieved as plaintext. Never log secret values + // in production code — platform logs are visible to operators and may be persisted. + // This line is shown for demonstration only; remove it in any real application. + println!("PASSWORD: {}", password); self.add_http_request_header("x-env-username", &username); + // WARNING: Forwarding a secret in a request header exposes it to the upstream origin + // and any intermediary that can inspect headers. Only do this when the upstream + // channel is trusted and the header is required by the destination API. self.add_http_request_header("x-env-password", &password); Action::Continue diff --git a/examples/http/basic/cache/Cargo.lock b/examples/http/basic/cache/Cargo.lock new file mode 100644 index 0000000..73701c0 --- /dev/null +++ b/examples/http/basic/cache/Cargo.lock @@ -0,0 +1,497 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cache_basic" +version = "0.1.0" +dependencies = [ + "anyhow", + "fastedge", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fastedge" +version = "0.3.5" +dependencies = [ + "bytes", + "fastedge-derive", + "http", + "mime", + "thiserror", + "wit-bindgen", +] + +[[package]] +name = "fastedge-derive" +version = "0.3.5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "bitflags", + "futures", + "once_cell", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/fastedge-plugin-source/generate-docs.sh b/fastedge-plugin-source/generate-docs.sh index 6381563..141db2c 100755 --- a/fastedge-plugin-source/generate-docs.sh +++ b/fastedge-plugin-source/generate-docs.sh @@ -210,6 +210,8 @@ $(cat "$full_path") # Existing Content for docs/$target Use this as the baseline. Preserve all accurate content and manual additions. Only change what is incorrect, incomplete, or missing per the source code. Keep sections not covered by the instructions above. Apply table formatting rules to all tables. +If the existing content is already accurate against the source code, output it verbatim with zero preamble or acknowledgement — your output starts at the # of the level-1 heading regardless of whether you made changes. + $existing_doc @@ -223,7 +225,7 @@ $existing_doc # sometimes produces conversational preamble or asks for permission. local prompt prompt="$(cat < "$tmpfile" <<<"$prompt" - # Validate: first non-empty line must start with # - local first_line - first_line=$(grep -m1 '.' "$tmpfile" || true) - if [[ "$first_line" == \#* ]]; then - mv "$tmpfile" "$DOCS_DIR/$target" + # Validate + salvage. The model intermittently leaks a conversational + # preamble like "I'll write the markdown now." before the real document, + # despite the OUTPUT CONSTRAINT in the prompt. Rather than discard those + # outputs and retry (wasting API quota on otherwise-good content), find + # the first level-1 heading and treat everything from there forward as + # the doc. The original is still saved to .failures/ so the prompt can + # be tuned later. + local stripped + # Fence-aware level-1-only salvage: track ``` fences so that #-prefixed + # lines inside a fence (shell comments, #include, etc.) don't trigger the + # heading-detection scan. Fence delimiter lines are NOT skipped — they fall + # through to `found { print }` so fenced code blocks are preserved intact + # in the output. Only a bare `# ` heading outside a fence sets found=1. + stripped=$(awk '/^```/ { in_fence = !in_fence } !in_fence && /^#[^#]/ { found=1 } found' "$tmpfile") + + # Post-strip validation: confirm the salvaged output actually starts with a + # level-1 heading. If awk matched something that looks like `#!` (shebang) + # or another edge case, reject it and retry rather than silently writing junk. + local first_nonempty + first_nonempty=$(printf '%s\n' "$stripped" | grep -m1 '.') + if [ -n "$stripped" ] && [[ "$first_nonempty" =~ ^#[^#] ]]; then + # Detect preamble: anything before the first level-1 heading is preamble. + local first_heading_line + first_heading_line=$(grep -n -m1 '^#[^#]' "$tmpfile" | cut -d: -f1) + if [ "${first_heading_line:-1}" -gt 1 ]; then + local preamble_copy="$failure_dir/${target}.preamble.attempt-${attempt}.$(date +%s).md" + cp "$tmpfile" "$preamble_copy" + echo " Stripped $((first_heading_line - 1)) preamble line(s) from $target (attempt $attempt) — original saved to $preamble_copy" + fi + printf '%s\n' "$stripped" > "$DOCS_DIR/$target" + rm -f "$tmpfile" echo " Done: docs/$target" return 0 fi - echo " Attempt $attempt/$max_attempts failed for $target (got conversational output), retrying..." + # No valid level-1 heading found — genuine failure. Save and retry. + local failed_copy="$failure_dir/${target}.attempt-${attempt}.$(date +%s).md" + cp "$tmpfile" "$failed_copy" + echo " Attempt $attempt/$max_attempts failed for $target (no level-1 heading found in output) — saved to $failed_copy, retrying..." attempt=$((attempt + 1)) done diff --git a/fastedge-plugin-source/manifest.json b/fastedge-plugin-source/manifest.json index b31495d..537e74f 100644 --- a/fastedge-plugin-source/manifest.json +++ b/fastedge-plugin-source/manifest.json @@ -1,7 +1,7 @@ { "$schema": "https://fastedge-plugin-source/manifest/v1", "repo_id": "fastedge-sdk-rust", - "version": "1.4.0", + "version": "1.5.0", "sources": { "sdk-api": { "files": ["docs/SDK_API.md"], @@ -19,6 +19,12 @@ "description": "CDN app guide — proxy-wasm lifecycle, fastedge::proxywasm::* API surface, request/response manipulation" }, + "quickstart": { + "files": ["docs/quickstart.md"], + "required": true, + "description": "Installation, project setup, async vs sync handler, basic build for HTTP and CDN apps" + }, + "http-hello-world-blueprint": { "files": [ "examples/http/wasi/hello_world/src/lib.rs", @@ -331,6 +337,439 @@ ], "required": true, "description": "CDN Cache Control example — docs pattern extraction" + }, + + "cdn-convert-image-blueprint": { + "files": [ + "examples/cdn/convert_image/src/lib.rs", + "examples/cdn/convert_image/Cargo.toml", + "examples/cdn/convert_image/README.md" + ], + "required": true, + "description": "CDN Image Conversion example — scaffold blueprint extraction" + }, + "cdn-convert-image-pattern": { + "files": [ + "examples/cdn/convert_image/src/lib.rs", + "examples/cdn/convert_image/Cargo.toml", + "examples/cdn/convert_image/README.md" + ], + "required": true, + "description": "CDN Image Conversion example — docs pattern extraction" + }, + + "cdn-custom-blueprint": { + "files": [ + "examples/cdn/custom/src/lib.rs", + "examples/cdn/custom/Cargo.toml", + "examples/cdn/custom/README.md" + ], + "required": true, + "description": "CDN Custom Response example — scaffold blueprint extraction" + }, + "cdn-custom-pattern": { + "files": [ + "examples/cdn/custom/src/lib.rs", + "examples/cdn/custom/Cargo.toml", + "examples/cdn/custom/README.md" + ], + "required": true, + "description": "CDN Custom Response example — docs pattern extraction" + }, + + "cdn-log-time-blueprint": { + "files": [ + "examples/cdn/log_time/src/lib.rs", + "examples/cdn/log_time/Cargo.toml", + "examples/cdn/log_time/README.md" + ], + "required": true, + "description": "CDN Log Time example — scaffold blueprint extraction" + }, + "cdn-log-time-pattern": { + "files": [ + "examples/cdn/log_time/src/lib.rs", + "examples/cdn/log_time/Cargo.toml", + "examples/cdn/log_time/README.md" + ], + "required": true, + "description": "CDN Log Time example — docs pattern extraction" + }, + + "cdn-md2html-blueprint": { + "files": [ + "examples/cdn/md2html/src/lib.rs", + "examples/cdn/md2html/Cargo.toml", + "examples/cdn/md2html/README.md" + ], + "required": true, + "description": "CDN Markdown-to-HTML example — scaffold blueprint extraction" + }, + "cdn-md2html-pattern": { + "files": [ + "examples/cdn/md2html/src/lib.rs", + "examples/cdn/md2html/Cargo.toml", + "examples/cdn/md2html/README.md" + ], + "required": true, + "description": "CDN Markdown-to-HTML example — docs pattern extraction" + }, + + "http-basic-hello-world-blueprint": { + "files": [ + "examples/http/basic/hello_world/src/lib.rs", + "examples/http/basic/hello_world/Cargo.toml", + "examples/http/basic/hello_world/README.md" + ], + "required": true, + "description": "HTTP Basic Hello World example (sync handler) — scaffold blueprint extraction" + }, + "http-basic-hello-world-pattern": { + "files": [ + "examples/http/basic/hello_world/src/lib.rs", + "examples/http/basic/hello_world/Cargo.toml", + "examples/http/basic/hello_world/README.md" + ], + "required": true, + "description": "HTTP Basic Hello World example (sync handler) — docs pattern extraction" + }, + + "http-basic-api-wrapper-blueprint": { + "files": [ + "examples/http/basic/api_wrapper/src/lib.rs", + "examples/http/basic/api_wrapper/Cargo.toml", + "examples/http/basic/api_wrapper/README.md" + ], + "required": true, + "description": "HTTP Basic API Wrapper example — scaffold blueprint extraction" + }, + "http-basic-api-wrapper-pattern": { + "files": [ + "examples/http/basic/api_wrapper/src/lib.rs", + "examples/http/basic/api_wrapper/Cargo.toml", + "examples/http/basic/api_wrapper/README.md" + ], + "required": true, + "description": "HTTP Basic API Wrapper example — docs pattern extraction" + }, + + "http-basic-backend-blueprint": { + "files": [ + "examples/http/basic/backend/src/lib.rs", + "examples/http/basic/backend/Cargo.toml", + "examples/http/basic/backend/README.md" + ], + "required": true, + "description": "HTTP Basic Backend Proxy example — scaffold blueprint extraction" + }, + "http-basic-backend-pattern": { + "files": [ + "examples/http/basic/backend/src/lib.rs", + "examples/http/basic/backend/Cargo.toml", + "examples/http/basic/backend/README.md" + ], + "required": true, + "description": "HTTP Basic Backend Proxy example — docs pattern extraction" + }, + + "http-basic-cache-blueprint": { + "files": [ + "examples/http/basic/cache/src/lib.rs", + "examples/http/basic/cache/Cargo.toml" + ], + "required": true, + "description": "HTTP Basic Cache example — scaffold blueprint extraction" + }, + "http-basic-cache-pattern": { + "files": [ + "examples/http/basic/cache/src/lib.rs", + "examples/http/basic/cache/Cargo.toml" + ], + "required": true, + "description": "HTTP Basic Cache example — docs pattern extraction" + }, + + "http-basic-markdown-render-blueprint": { + "files": [ + "examples/http/basic/markdown_render/src/lib.rs", + "examples/http/basic/markdown_render/Cargo.toml", + "examples/http/basic/markdown_render/README.md" + ], + "required": true, + "description": "HTTP Basic Markdown Render example — scaffold blueprint extraction" + }, + "http-basic-markdown-render-pattern": { + "files": [ + "examples/http/basic/markdown_render/src/lib.rs", + "examples/http/basic/markdown_render/Cargo.toml", + "examples/http/basic/markdown_render/README.md" + ], + "required": true, + "description": "HTTP Basic Markdown Render example — docs pattern extraction" + }, + + "http-basic-outbound-fetch-blueprint": { + "files": [ + "examples/http/basic/outbound_fetch/src/lib.rs", + "examples/http/basic/outbound_fetch/Cargo.toml", + "examples/http/basic/outbound_fetch/README.md" + ], + "required": true, + "description": "HTTP Basic Outbound Fetch example — scaffold blueprint extraction" + }, + "http-basic-outbound-fetch-pattern": { + "files": [ + "examples/http/basic/outbound_fetch/src/lib.rs", + "examples/http/basic/outbound_fetch/Cargo.toml", + "examples/http/basic/outbound_fetch/README.md" + ], + "required": true, + "description": "HTTP Basic Outbound Fetch example — docs pattern extraction" + }, + + "http-basic-print-blueprint": { + "files": [ + "examples/http/basic/print/src/lib.rs", + "examples/http/basic/print/Cargo.toml", + "examples/http/basic/print/README.md" + ], + "required": true, + "description": "HTTP Basic Print / Logging example — scaffold blueprint extraction" + }, + "http-basic-print-pattern": { + "files": [ + "examples/http/basic/print/src/lib.rs", + "examples/http/basic/print/Cargo.toml", + "examples/http/basic/print/README.md" + ], + "required": true, + "description": "HTTP Basic Print / Logging example — docs pattern extraction" + }, + + "http-basic-s3upload-blueprint": { + "files": [ + "examples/http/basic/s3upload/src/lib.rs", + "examples/http/basic/s3upload/Cargo.toml", + "examples/http/basic/s3upload/README.md" + ], + "required": true, + "description": "HTTP Basic S3 Upload example — scaffold blueprint extraction" + }, + "http-basic-s3upload-pattern": { + "files": [ + "examples/http/basic/s3upload/src/lib.rs", + "examples/http/basic/s3upload/Cargo.toml", + "examples/http/basic/s3upload/README.md" + ], + "required": true, + "description": "HTTP Basic S3 Upload example — docs pattern extraction" + }, + + "http-basic-secret-blueprint": { + "files": [ + "examples/http/basic/secret/src/lib.rs", + "examples/http/basic/secret/Cargo.toml", + "examples/http/basic/secret/README.md" + ], + "required": true, + "description": "HTTP Basic Secrets example — scaffold blueprint extraction" + }, + "http-basic-secret-pattern": { + "files": [ + "examples/http/basic/secret/src/lib.rs", + "examples/http/basic/secret/Cargo.toml", + "examples/http/basic/secret/README.md" + ], + "required": true, + "description": "HTTP Basic Secrets example — docs pattern extraction" + }, + + "http-basic-smart-switch-blueprint": { + "files": [ + "examples/http/basic/smart_switch/src/lib.rs", + "examples/http/basic/smart_switch/Cargo.toml", + "examples/http/basic/smart_switch/README.md" + ], + "required": true, + "description": "HTTP Basic Smart Switch / Routing example — scaffold blueprint extraction" + }, + "http-basic-smart-switch-pattern": { + "files": [ + "examples/http/basic/smart_switch/src/lib.rs", + "examples/http/basic/smart_switch/Cargo.toml", + "examples/http/basic/smart_switch/README.md" + ], + "required": true, + "description": "HTTP Basic Smart Switch / Routing example — docs pattern extraction" + }, + + "http-basic-watermark-blueprint": { + "files": [ + "examples/http/basic/watermark/src/lib.rs", + "examples/http/basic/watermark/Cargo.toml", + "examples/http/basic/watermark/README.md" + ], + "required": true, + "description": "HTTP Basic Watermark example — scaffold blueprint extraction" + }, + "http-basic-watermark-pattern": { + "files": [ + "examples/http/basic/watermark/src/lib.rs", + "examples/http/basic/watermark/Cargo.toml", + "examples/http/basic/watermark/README.md" + ], + "required": true, + "description": "HTTP Basic Watermark example — docs pattern extraction" + }, + + "http-wasi-cache-blueprint": { + "files": [ + "examples/http/wasi/cache/src/lib.rs", + "examples/http/wasi/cache/Cargo.toml" + ], + "required": true, + "description": "HTTP WASI Cache example — scaffold blueprint extraction" + }, + "http-wasi-cache-pattern": { + "files": [ + "examples/http/wasi/cache/src/lib.rs", + "examples/http/wasi/cache/Cargo.toml" + ], + "required": true, + "description": "HTTP WASI Cache example — docs pattern extraction" + }, + + "http-wasi-geo-redirect-blueprint": { + "files": [ + "examples/http/wasi/geo_redirect/src/lib.rs", + "examples/http/wasi/geo_redirect/Cargo.toml", + "examples/http/wasi/geo_redirect/README.md" + ], + "required": true, + "description": "HTTP WASI Geo Redirect example — scaffold blueprint extraction" + }, + "http-wasi-geo-redirect-pattern": { + "files": [ + "examples/http/wasi/geo_redirect/src/lib.rs", + "examples/http/wasi/geo_redirect/Cargo.toml", + "examples/http/wasi/geo_redirect/README.md" + ], + "required": true, + "description": "HTTP WASI Geo Redirect example — docs pattern extraction" + }, + + "http-wasi-headers-blueprint": { + "files": [ + "examples/http/wasi/headers/src/lib.rs", + "examples/http/wasi/headers/Cargo.toml", + "examples/http/wasi/headers/README.md" + ], + "required": true, + "description": "HTTP WASI Headers Manipulation example — scaffold blueprint extraction" + }, + "http-wasi-headers-pattern": { + "files": [ + "examples/http/wasi/headers/src/lib.rs", + "examples/http/wasi/headers/Cargo.toml", + "examples/http/wasi/headers/README.md" + ], + "required": true, + "description": "HTTP WASI Headers Manipulation example — docs pattern extraction" + }, + + "http-wasi-large-env-variable-blueprint": { + "files": [ + "examples/http/wasi/large_env_variable/src/lib.rs", + "examples/http/wasi/large_env_variable/Cargo.toml", + "examples/http/wasi/large_env_variable/README.md" + ], + "required": true, + "description": "HTTP WASI Large Env Variable / Dictionary example — scaffold blueprint extraction" + }, + "http-wasi-large-env-variable-pattern": { + "files": [ + "examples/http/wasi/large_env_variable/src/lib.rs", + "examples/http/wasi/large_env_variable/Cargo.toml", + "examples/http/wasi/large_env_variable/README.md" + ], + "required": true, + "description": "HTTP WASI Large Env Variable / Dictionary example — docs pattern extraction" + }, + + "http-wasi-outbound-fetch-blueprint": { + "files": [ + "examples/http/wasi/outbound_fetch/src/lib.rs", + "examples/http/wasi/outbound_fetch/Cargo.toml", + "examples/http/wasi/outbound_fetch/README.md" + ], + "required": true, + "description": "HTTP WASI Outbound Fetch example — scaffold blueprint extraction" + }, + "http-wasi-outbound-fetch-pattern": { + "files": [ + "examples/http/wasi/outbound_fetch/src/lib.rs", + "examples/http/wasi/outbound_fetch/Cargo.toml", + "examples/http/wasi/outbound_fetch/README.md" + ], + "required": true, + "description": "HTTP WASI Outbound Fetch example — docs pattern extraction" + }, + + "http-wasi-secret-rollover-blueprint": { + "files": [ + "examples/http/wasi/secret_rollover/src/lib.rs", + "examples/http/wasi/secret_rollover/Cargo.toml", + "examples/http/wasi/secret_rollover/README.md" + ], + "required": true, + "description": "HTTP WASI Secret Rollover example — scaffold blueprint extraction" + }, + "http-wasi-secret-rollover-pattern": { + "files": [ + "examples/http/wasi/secret_rollover/src/lib.rs", + "examples/http/wasi/secret_rollover/Cargo.toml", + "examples/http/wasi/secret_rollover/README.md" + ], + "required": true, + "description": "HTTP WASI Secret Rollover example — docs pattern extraction" + }, + + "http-wasi-simple-fetch-blueprint": { + "files": [ + "examples/http/wasi/simple_fetch/src/lib.rs", + "examples/http/wasi/simple_fetch/Cargo.toml", + "examples/http/wasi/simple_fetch/README.md" + ], + "required": true, + "description": "HTTP WASI Simple Fetch example — scaffold blueprint extraction" + }, + "http-wasi-simple-fetch-pattern": { + "files": [ + "examples/http/wasi/simple_fetch/src/lib.rs", + "examples/http/wasi/simple_fetch/Cargo.toml", + "examples/http/wasi/simple_fetch/README.md" + ], + "required": true, + "description": "HTTP WASI Simple Fetch example — docs pattern extraction" + }, + + "http-wasi-variables-and-secrets-blueprint": { + "files": [ + "examples/http/wasi/variables_and_secrets/src/lib.rs", + "examples/http/wasi/variables_and_secrets/Cargo.toml", + "examples/http/wasi/variables_and_secrets/README.md" + ], + "required": true, + "description": "HTTP WASI Variables and Secrets example — scaffold blueprint extraction" + }, + "http-wasi-variables-and-secrets-pattern": { + "files": [ + "examples/http/wasi/variables_and_secrets/src/lib.rs", + "examples/http/wasi/variables_and_secrets/Cargo.toml", + "examples/http/wasi/variables_and_secrets/README.md" + ], + "required": true, + "description": "HTTP WASI Variables and Secrets example — docs pattern extraction" } }, "target_mapping": { @@ -347,6 +786,11 @@ "section": null }, + "quickstart": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/quickstart-rust.md", + "section": null + }, + "http-hello-world-blueprint": { "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/base-rust.md", "section": null @@ -499,6 +943,213 @@ "cdn-cache-control-pattern": { "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-cache-control-rust.md", "section": null + }, + + "cdn-convert-image-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/cdn/convert-image-rust.md", + "section": null + }, + "cdn-convert-image-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-convert-image-rust.md", + "section": null + }, + + "cdn-custom-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/cdn/custom-rust.md", + "section": null + }, + "cdn-custom-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-custom-rust.md", + "section": null + }, + + "cdn-log-time-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/cdn/log-time-rust.md", + "section": null + }, + "cdn-log-time-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-log-time-rust.md", + "section": null + }, + + "cdn-md2html-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/cdn/md2html-rust.md", + "section": null + }, + "cdn-md2html-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-md2html-rust.md", + "section": null + }, + + "http-basic-hello-world-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/hello-world-basic-rust.md", + "section": null + }, + "http-basic-hello-world-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-hello-world-basic-rust.md", + "section": null + }, + + "http-basic-api-wrapper-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/api-wrapper-basic-rust.md", + "section": null + }, + "http-basic-api-wrapper-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-api-wrapper-basic-rust.md", + "section": null + }, + + "http-basic-backend-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/backend-basic-rust.md", + "section": null + }, + "http-basic-backend-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-backend-basic-rust.md", + "section": null + }, + + "http-basic-cache-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/cache-basic-rust.md", + "section": null + }, + "http-basic-cache-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-cache-basic-rust.md", + "section": null + }, + + "http-basic-markdown-render-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/markdown-render-basic-rust.md", + "section": null + }, + "http-basic-markdown-render-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-markdown-render-basic-rust.md", + "section": null + }, + + "http-basic-outbound-fetch-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/outbound-fetch-basic-rust.md", + "section": null + }, + "http-basic-outbound-fetch-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-outbound-fetch-basic-rust.md", + "section": null + }, + + "http-basic-print-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/print-basic-rust.md", + "section": null + }, + "http-basic-print-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-print-basic-rust.md", + "section": null + }, + + "http-basic-s3upload-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/s3upload-basic-rust.md", + "section": null + }, + "http-basic-s3upload-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-s3upload-basic-rust.md", + "section": null + }, + + "http-basic-secret-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/secret-basic-rust.md", + "section": null + }, + "http-basic-secret-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-secret-basic-rust.md", + "section": null + }, + + "http-basic-smart-switch-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/smart-switch-basic-rust.md", + "section": null + }, + "http-basic-smart-switch-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-smart-switch-basic-rust.md", + "section": null + }, + + "http-basic-watermark-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/watermark-basic-rust.md", + "section": null + }, + "http-basic-watermark-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-watermark-basic-rust.md", + "section": null + }, + + "http-wasi-cache-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/cache-wasi-rust.md", + "section": null + }, + "http-wasi-cache-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-cache-wasi-rust.md", + "section": null + }, + + "http-wasi-geo-redirect-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/geo-redirect-wasi-rust.md", + "section": null + }, + "http-wasi-geo-redirect-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-geo-redirect-wasi-rust.md", + "section": null + }, + + "http-wasi-headers-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/headers-wasi-rust.md", + "section": null + }, + "http-wasi-headers-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-headers-wasi-rust.md", + "section": null + }, + + "http-wasi-large-env-variable-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/large-env-variable-wasi-rust.md", + "section": null + }, + "http-wasi-large-env-variable-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-large-env-variable-wasi-rust.md", + "section": null + }, + + "http-wasi-outbound-fetch-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/outbound-fetch-wasi-rust.md", + "section": null + }, + "http-wasi-outbound-fetch-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-outbound-fetch-wasi-rust.md", + "section": null + }, + + "http-wasi-secret-rollover-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/secret-rollover-wasi-rust.md", + "section": null + }, + "http-wasi-secret-rollover-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-secret-rollover-wasi-rust.md", + "section": null + }, + + "http-wasi-simple-fetch-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/simple-fetch-wasi-rust.md", + "section": null + }, + "http-wasi-simple-fetch-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-simple-fetch-wasi-rust.md", + "section": null + }, + + "http-wasi-variables-and-secrets-blueprint": { + "reference_file": "plugins/gcore-fastedge/skills/scaffold/reference/http/variables-and-secrets-wasi-rust.md", + "section": null + }, + "http-wasi-variables-and-secrets-pattern": { + "reference_file": "plugins/gcore-fastedge/skills/fastedge-docs/reference/http/examples-variables-and-secrets-wasi-rust.md", + "section": null } }, "validation": { diff --git a/llms.txt b/llms.txt index 00742d5..865b16d 100644 --- a/llms.txt +++ b/llms.txt @@ -1,6 +1,6 @@ # fastedge -> Documentation for the `fastedge` crate (v0.3.5) — a Rust SDK for building edge computing applications that compile to WebAssembly and run on the FastEdge platform. +> Documentation for the `fastedge` crate (v0.4.0) — a Rust SDK for building edge computing applications that compile to WebAssembly and run on the FastEdge platform. ## Documentation