Conversation
There was a problem hiding this comment.
Pull request overview
Refactors the repository’s Rust examples into standalone projects organized under examples/http/basic, examples/http/wasi, and examples/cdn, and updates the top-level Cargo workspace so the core SDK builds independently of examples.
Changes:
- Reorganized examples into categorized subtrees with per-example
Cargo.tomlfiles and new README docs. - Added new/updated example implementations for sync HTTP (
fastedge), async WASI-HTTP (wstd), and CDN proxy-wasm. - Removed examples from the root workspace membership and updated
.gitignorefor FastEdge debugger artifacts.
Reviewed changes
Copilot reviewed 90 out of 104 changed files in this pull request and generated 18 comments.
Show a summary per file
| File | Description |
|---|---|
| examples/watermark/Cargo.toml | Removed legacy top-level example crate |
| examples/secret/Cargo.toml | Removed legacy top-level example crate |
| examples/print/Cargo.toml | Removed legacy top-level example crate |
| examples/key-value/Cargo.toml | Removed legacy top-level example crate |
| examples/backend/Cargo.toml | Removed legacy top-level example crate |
| examples/api-wrapper/Cargo.toml | Removed legacy top-level example crate |
| examples/dummy/src/lib.rs | Removed legacy dummy example code |
| examples/dummy/Cargo.toml | Removed legacy dummy example crate |
| examples/README.md | New categorized examples index |
| examples/http/wasi/variables_and_secrets/src/lib.rs | New WASI env+secret example handler |
| examples/http/wasi/variables_and_secrets/README.md | Docs for WASI env+secret example |
| examples/http/wasi/variables_and_secrets/Cargo.toml | Standalone WASI env+secret crate config |
| examples/http/wasi/simple_fetch/src/lib.rs | WASI outbound fetch example implementation |
| examples/http/wasi/simple_fetch/README.md | Updated docs/title + backlink |
| examples/http/wasi/simple_fetch/Cargo.toml | Renamed crate + component metadata update |
| examples/http/wasi/secret_rollover/src/lib.rs | New slot-based secret rotation example |
| examples/http/wasi/secret_rollover/README.md | Docs for secret rollover example |
| examples/http/wasi/secret_rollover/Cargo.toml | Standalone secret rollover crate config |
| examples/http/wasi/outbound_fetch/src/lib.rs | New WASI JSON fetch+transform example |
| examples/http/wasi/outbound_fetch/README.md | Docs for outbound fetch (WASI) |
| examples/http/wasi/outbound_fetch/Cargo.toml | Standalone outbound fetch (WASI) config |
| examples/http/wasi/key_value/src/lib.rs | New WASI KV operations via query params |
| examples/http/wasi/key_value/README.md | Docs for key value (WASI) |
| examples/http/wasi/key_value/Cargo.toml | Standalone key value (WASI) crate config |
| examples/http/wasi/hello_world/src/lib.rs | New WASI hello world handler |
| examples/http/wasi/hello_world/README.md | Docs for WASI hello world |
| examples/http/wasi/hello_world/Cargo.toml | Standalone WASI hello world config |
| examples/http/wasi/hello_world/Cargo.lock | Added per-example lockfile |
| examples/http/wasi/headers/src/lib.rs | New WASI headers echo example |
| examples/http/wasi/headers/README.md | Docs for WASI headers example |
| examples/http/wasi/headers/Cargo.toml | Standalone WASI headers config |
| examples/http/wasi/geo_redirect/src/lib.rs | New WASI geo redirect example |
| examples/http/wasi/geo_redirect/README.md | Docs for WASI geo redirect |
| examples/http/wasi/geo_redirect/Cargo.toml | Standalone WASI geo redirect config |
| examples/http/basic/watermark/src/sample.png | Added embedded watermark asset |
| examples/http/basic/watermark/src/lib.rs | New S3 fetch + watermarking example |
| examples/http/basic/watermark/README.md | Docs for watermark example |
| examples/http/basic/watermark/Cargo.toml | Standalone watermark crate config |
| examples/http/basic/variables_and_secrets/src/lib.rs | New sync env+secret example handler |
| examples/http/basic/variables_and_secrets/README.md | Docs for sync env+secret example |
| examples/http/basic/variables_and_secrets/Cargo.toml | Standalone sync env+secret config |
| examples/http/basic/secret/src/lib.rs | New sync secret + effective_at example |
| examples/http/basic/secret/README.md | Docs for secret example |
| examples/http/basic/secret/Cargo.toml | Standalone secret crate config |
| examples/http/basic/print/src/lib.rs | New sync request printer example |
| examples/http/basic/print/README.md | Docs for print example |
| examples/http/basic/print/Cargo.toml | Standalone print crate config |
| examples/http/basic/outbound_fetch/src/lib.rs | New sync JSON fetch+transform example |
| examples/http/basic/outbound_fetch/README.md | Docs for outbound fetch example |
| examples/http/basic/outbound_fetch/Cargo.toml | Standalone outbound fetch config |
| examples/http/basic/markdown_render/src/lib.rs | Markdown fetch + HTML render example |
| examples/http/basic/markdown_render/README.md | Docs for markdown render example |
| examples/http/basic/markdown_render/Cargo.toml | Renamed crate + dependency source updates |
| examples/http/basic/key_value/src/lib.rs | Sync KV store example implementation |
| examples/http/basic/key_value/README.md | Docs for key value example |
| examples/http/basic/key_value/Cargo.toml | Standalone key value crate config |
| examples/http/basic/hello_world/src/lib.rs | New sync hello world handler |
| examples/http/basic/hello_world/README.md | Docs for sync hello world |
| examples/http/basic/hello_world/Cargo.toml | Standalone sync hello world config |
| examples/http/basic/hello_world/Cargo.lock | Added per-example lockfile |
| examples/http/basic/headers/src/lib.rs | New sync headers echo example |
| examples/http/basic/headers/README.md | Docs for headers example |
| examples/http/basic/headers/Cargo.toml | Standalone headers crate config |
| examples/http/basic/geo_redirect/src/lib.rs | New sync geo redirect example |
| examples/http/basic/geo_redirect/README.md | Docs for geo redirect example |
| examples/http/basic/geo_redirect/Cargo.toml | Standalone geo redirect crate config |
| examples/http/basic/backend/src/lib.rs | New backend proxy example implementation |
| examples/http/basic/backend/README.md | Docs for backend example |
| examples/http/basic/backend/Cargo.toml | Standalone backend crate config |
| examples/http/basic/api_wrapper/src/lib.rs | SmartThings API wrapper example |
| examples/http/basic/api_wrapper/README.md | Docs for API wrapper example |
| examples/http/basic/api_wrapper/Cargo.toml | Standalone API wrapper config |
| examples/cdn/.cargo/config.toml | Sets default wasm32-wasip1 target for CDN examples |
| examples/cdn/variables_and_secrets/src/lib.rs | Proxy-wasm env+secret forwarding example |
| examples/cdn/variables_and_secrets/README.md | Docs for CDN env+secret example |
| examples/cdn/variables_and_secrets/Cargo.toml | Standalone CDN env+secret config |
| examples/cdn/properties/src/lib.rs | Proxy-wasm request property extraction example |
| examples/cdn/properties/README.md | Docs for properties example |
| examples/cdn/properties/Cargo.toml | Standalone properties config |
| examples/cdn/log_time/src/lib.rs | Proxy-wasm timestamp logging example |
| examples/cdn/log_time/README.md | Docs for log_time example |
| examples/cdn/log_time/Cargo.toml | Standalone log_time config |
| examples/cdn/key_value/src/lib.rs | Proxy-wasm KV operations example |
| examples/cdn/key_value/README.md | Docs for CDN key_value example |
| examples/cdn/key_value/Cargo.toml | Standalone CDN key_value config |
| examples/cdn/http_call/src/lib.rs | Proxy-wasm async HTTP call example |
| examples/cdn/http_call/README.md | Docs for http_call example |
| examples/cdn/http_call/Cargo.toml | Standalone http_call config |
| examples/cdn/headers/src/lib.rs | Proxy-wasm header manipulation/validation example |
| examples/cdn/headers/README.md | Docs for CDN headers example |
| examples/cdn/headers/Cargo.toml | Standalone CDN headers config |
| examples/cdn/headers/Cargo.lock | Added per-example lockfile |
| examples/cdn/geo_redirect/src/lib.rs | Proxy-wasm geo-based origin routing example |
| examples/cdn/geo_redirect/README.md | Docs for CDN geo_redirect example |
| examples/cdn/geo_redirect/Cargo.toml | Standalone CDN geo_redirect config |
| examples/cdn/custom/src/lib.rs | Proxy-wasm custom status code/delay example |
| examples/cdn/custom/README.md | Docs for custom example |
| examples/cdn/custom/Cargo.toml | Standalone custom config |
| examples/cdn/body/src/lib.rs | Proxy-wasm request/response body redaction example |
| examples/cdn/body/README.md | Docs for body example |
| examples/cdn/body/Cargo.toml | Standalone body config |
| Cargo.toml | Removed examples from workspace members |
| Cargo.lock | Updated lockfile after workspace change |
| .gitignore | Ignore FastEdge debugger artifacts |
Comments suppressed due to low confidence (1)
examples/cdn/geo_redirect/README.md:11
- README configuration does not match the implementation: the code reads the fallback origin from environment variable
DEFAULT, but the README listsBASE_ORIGIN. Please update the README to match the code (or rename the dictionary key in code) to avoid a misconfigured example.
## Configuration
- Environment variable: `BASE_ORIGIN` — fallback origin URL
- Environment variable: `<COUNTRY_CODE>` — optional per-country origin URLs (e.g. `US`, `DE`, `GB`)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| [dependencies] | ||
| fastedge = "0.3" | ||
| urlencoding = "2.1" | ||
| url = "2.3" | ||
| image = "0.24" | ||
| rusty-s3 = "0.5" | ||
| anyhow = "1" |
There was a problem hiding this comment.
urlencoding is declared as a dependency but there are no usages in this crate (the only urlencoding usage in examples appears in examples/http/basic/backend). Please remove it to keep the example’s dependency list minimal, or add the missing usage if intended.
| let upstream_req = Request::builder() | ||
| .uri("http://jsonplaceholder.typicode.com/users") | ||
| .body(Body::empty())?; | ||
|
|
||
| let upstream_resp = fastedge::send_request(upstream_req).map_err(Error::msg)?; | ||
|
|
||
| let body_bytes = upstream_resp.body().to_vec(); | ||
| let users: Value = serde_json::from_slice(&body_bytes)?; | ||
|
|
||
| let sliced_users = match users.as_array() { | ||
| Some(arr) => Value::Array(arr.iter().take(5).cloned().collect()), | ||
| None => Value::Array(vec![]), | ||
| }; | ||
|
|
||
| let result = json!({ | ||
| "users": sliced_users, | ||
| "total": 5, | ||
| "skip": 0, | ||
| "limit": 30, | ||
| }); |
There was a problem hiding this comment.
The JSON metadata is inconsistent with the actual behavior: you return take(5) users but set limit to 30. Also, the URL uses plain HTTP. Consider switching to https://... and making limit match the number of returned users (or actually return limit users).
| let upstream_req = Request::get("http://jsonplaceholder.typicode.com/users") | ||
| .body(Body::empty()) | ||
| .map_err(|e| anyhow!("failed to build request: {e}"))?; | ||
|
|
||
| let client = Client::new(); | ||
| let upstream_resp = client | ||
| .send(upstream_req) | ||
| .await | ||
| .map_err(|e| anyhow!("request failed: {e}"))?; | ||
|
|
||
| let (_, mut body) = upstream_resp.into_parts(); | ||
| let body_bytes = body.contents().await?; | ||
| let users: Value = serde_json::from_slice(body_bytes)?; | ||
|
|
||
| let sliced_users = match users.as_array() { | ||
| Some(arr) => Value::Array(arr.iter().take(5).cloned().collect()), | ||
| None => Value::Array(vec![]), | ||
| }; | ||
|
|
||
| let result = json!({ | ||
| "users": sliced_users, | ||
| "total": 5, | ||
| "skip": 0, | ||
| "limit": 30, | ||
| }); |
There was a problem hiding this comment.
Same issue as the sync variant: the upstream URL is http://... and the response metadata says limit: 30 while only 5 users are returned (take(5)). Please use HTTPS and make limit/total consistent with the returned slice.
| ## How Slots Work | ||
|
|
||
| Slots use a `>=` matching rule: the slot with the highest value that is `<=` the requested `effective_at` is returned. This supports both index-based and timestamp-based rotation patterns. See the [secret rollover documentation](../../../../FastEdge-sdk-js/github-pages/src/content/docs/reference/fastedge/secret/get-secret-effective-at.md) for detailed examples. |
There was a problem hiding this comment.
This description is self-contradictory: it says slots use a >= matching rule but then describes selecting the highest slot value that is <= the requested time. Please correct the operator in the docs (likely <=) so users implement rotation correctly.
| let username = dictionary::get("USERNAME").unwrap_or_default(); | ||
| let password = secret::get("PASSWORD") | ||
| .ok() | ||
| .flatten() | ||
| .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(); | ||
|
|
||
| self.add_http_request_header("x-env-username", &username); | ||
| self.add_http_request_header("x-env-password", &password); |
There was a problem hiding this comment.
This example logs the secret value (PASSWORD) at Info level. Even in examples, logging secrets is unsafe because logs are often exported/retained. Prefer logging only whether the secret was present (or a redacted placeholder) and avoid emitting secret contents.
| let username = dictionary::get("USERNAME").unwrap_or_default(); | ||
|
|
||
| let password = match fastedge::secret::get("PASSWORD") { | ||
| Ok(Some(value)) => value, | ||
| _ => String::new(), | ||
| }; | ||
|
|
||
| Response::builder() | ||
| .status(StatusCode::OK) | ||
| .body(Body::from(format!( | ||
| "Username: {username}, Password: {password}" | ||
| ))) |
There was a problem hiding this comment.
This example returns the secret value (PASSWORD) directly in the response body. To avoid encouraging insecure patterns, consider redacting the secret (or returning only a boolean) and explicitly calling out in the README that secrets should not be echoed to clients.
| Demonstrates how to use `secret::get_effective_at()` with slots to support | ||
| secret rotation. Slots use a `>=` matching rule: the slot with the highest | ||
| value that is <= the requested `effective_at` is returned. | ||
|
|
There was a problem hiding this comment.
The comment says slots use a >= matching rule, but the next line describes selecting the highest slot value that is <= the requested effective_at. Please fix the operator in the comment to avoid misleading readers of the example.
|
|
||
| let query = std::str::from_utf8(&query).unwrap(); | ||
| println!("query={}", query); | ||
| let params = querystring::querify(query); |
There was a problem hiding this comment.
std::str::from_utf8(&query).unwrap() can panic if the query bytes are not valid UTF-8. Since this is request-derived data, prefer handling the Result (return an error response) or using String::from_utf8_lossy before parsing query parameters.
| let Some(extention) = self.get_property(vec![REQUEST_EXTENSION]) else { | ||
| self.send_http_response(555, vec![], None); | ||
| return Action::Pause; | ||
| }; | ||
| println!(" extention = {} ", String::from_utf8_lossy(&extention)); | ||
| self.add_http_response_header_bytes("request-extention", &extention); | ||
|
|
There was a problem hiding this comment.
Spelling: variable/header name extention / request-extention should be extension / request-extension for clarity and to match the property name (request.extension).
| self.set_http_request_header("new-header-01", None); | ||
| self.set_http_request_header_bytes("new-header-bytes-01", None); |
There was a problem hiding this comment.
set_http_request_header("new-header-01", None) / set_http_request_header_bytes(..., None) removes the header, but the later expected set includes new-header-01 with an empty value. Unless the host normalizes removals to empty values, this will cause the example to fail its own header-diff assertions. Either set Some("") for an empty value, or remove new-header-01 from the expected sets.
| self.set_http_request_header("new-header-01", None); | |
| self.set_http_request_header_bytes("new-header-bytes-01", None); | |
| self.set_http_request_header("new-header-01", Some("")); | |
| self.set_http_request_header_bytes("new-header-bytes-01", Some(b"")); |
No description provided.