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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |

Expand Down
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
4 changes: 3 additions & 1 deletion context/architecture/HOST_SDK_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion context/architecture/REQUEST_LIFECYCLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 7 additions & 1 deletion docs/CDN_APPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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 {}
Expand Down
2 changes: 1 addition & 1 deletion docs/INDEX.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
90 changes: 45 additions & 45 deletions docs/SDK_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand All @@ -25,7 +25,7 @@ For `#[fastedge::http]` (basic):

```toml
[dependencies]
fastedge = "0.3.5"
fastedge = "0.4.0"
anyhow = "1"

[lib]
Expand Down Expand Up @@ -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<u8>)` | `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<Body, serde_json::Error>` |
| 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<u8>)` | `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<Body, serde_json::Error>` |

```rust
use fastedge::body::Body;
Expand All @@ -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;
Expand All @@ -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`:

Expand All @@ -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>` / `&[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>` / `&[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};

Expand Down Expand Up @@ -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};
Expand All @@ -401,23 +401,23 @@ fn fetch(uri: &str) -> Result<String, Error> {

## 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 }
```

---
Expand All @@ -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` |

---

Expand Down
12 changes: 6 additions & 6 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Add dependencies to `Cargo.toml`:

```toml
[dependencies]
fastedge = "0.3.5"
fastedge = "0.4.0"
anyhow = "1"

[lib]
Expand Down Expand Up @@ -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/<target>/release/` directory. Neither path requires `cargo-component`.

Expand All @@ -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
Expand Down
10 changes: 10 additions & 0 deletions examples/cdn/ab_testing/fixtures/existing-cookie-a.live.json
Original file line number Diff line number Diff line change
@@ -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
}
}
10 changes: 10 additions & 0 deletions examples/cdn/ab_testing/fixtures/existing-cookie-b.live.json
Original file line number Diff line number Diff line change
@@ -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
}
}
6 changes: 6 additions & 0 deletions examples/cdn/ab_testing/fixtures/missing-config.live.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"expected": {
"status": 500,
"body": "App misconfigured - EXPERIMENT_NAME must be set"
}
}
9 changes: 9 additions & 0 deletions examples/cdn/ab_testing/fixtures/new-visitor.live.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"expected": {
"logs": ["A/B test \"homepage-hero\": variant "],
"headers": {
"set-cookie": { "contains": "fe_exp_homepage-hero=" }
},
"status": 200
}
}
12 changes: 4 additions & 8 deletions examples/cdn/ab_testing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
6 changes: 6 additions & 0 deletions examples/cdn/api_key/fixtures/happy-path.live.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"expected": {
"logs": ["API key validated successfully"],
"status": 200
}
}
7 changes: 7 additions & 0 deletions examples/cdn/api_key/fixtures/invalid-key.live.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"expected": {
"logs": ["API key validation failed"],
"status": 403,
"body": "Invalid API key"
}
}
9 changes: 9 additions & 0 deletions examples/cdn/api_key/fixtures/missing-header.live.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"expected": {
"status": 401,
"headers": {
"www-authenticate": "API-Key"
},
"body": "Missing X-API-Key header"
}
}
6 changes: 6 additions & 0 deletions examples/cdn/api_key/fixtures/missing-secret.live.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"expected": {
"status": 500,
"body": "App misconfigured"
}
}
Loading
Loading