diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..ce9f661 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,85 @@ +# Copilot PR Review Instructions — FastEdge-sdk-rust + +## Constitution + +This repository is `fastedge` (crate) — the Rust SDK for Gcore FastEdge. It provides the `#[fastedge::http]` and `#[wstd::http_server]` handler macros, type conversions, an outbound HTTP client, and ProxyWasm FFI wrappers for CDN apps. + +### Principles (enforce during review) + +1. **Handler preference** — `#[wstd::http_server]` (async, wasm32-wasip2) is the recommended handler for new HTTP apps. `#[fastedge::http]` is legacy. New examples must use `wstd`. +2. **No over-engineering** — Simple solutions over complex abstractions. Three similar lines > premature abstraction. +3. **Platform constraints** — Only stdout is captured; `eprintln!` output is silently lost. Flag any use of stderr in code or examples. +4. **CDN/HTTP separation** — CDN apps (proxy-wasm filters) and HTTP apps (standalone handlers) are independent application types with different architectures and lifecycles. Never mix their APIs. +5. **WIT submodule integrity** — `wit/` files come from `G-Core/FastEdge-wit` submodule. Never modify them directly. + +### Public API contract + +The public API surface is defined by: +- `src/lib.rs` — Core types (`Body`, `Error`), type conversions, `send_request` +- `derive/src/lib.rs` — `#[fastedge::http]` proc macro +- `src/proxywasm/` — ProxyWasm FFI wrappers (KV store, secrets, dictionary, utils) +- `src/http_client.rs` — Outbound HTTP client + +Changes to these surfaces require updated `docs/`, updated tests, and a semver-appropriate version bump. + +## Generated Content — `docs/` + +Files in `docs/` are **machine-generated** from source code by `./fastedge-plugin-source/generate-docs.sh`. They must not be edited by hand — manual changes will be silently overwritten on the next generation run. + +### When reviewing PRs that touch `docs/`: + +- **Never** suggest manual edits to any file in `docs/` +- If docs are stale or incorrect, suggest: **Run `./fastedge-plugin-source/generate-docs.sh`** +- If the generated output itself is wrong (e.g., wrong structure, missing section), the fix belongs in `fastedge-plugin-source/.generation-config.md`, not in `docs/` directly +- If a PR modifies `docs/` files without a corresponding source code change, flag it — the change should come from the generation script, not a hand-edit + +### When reviewing PRs that change source code covered by `docs/`: + +- Check whether the change affects the public API or user-facing behavior +- If yes, and `docs/` was not regenerated in the same PR, **request changes** with: + > Source code affecting public API was changed but docs/ was not regenerated. + > Run: `./fastedge-plugin-source/generate-docs.sh` + +## Documentation Freshness + +### Public API changes (must regenerate docs/) +- New, modified, or removed public types/functions in `src/lib.rs` +- Changes to `#[fastedge::http]` macro behavior in `derive/src/lib.rs` +- Changes to ProxyWasm wrapper APIs in `src/proxywasm/` +- Changes to outbound HTTP client in `src/http_client.rs` +- New or modified WIT interfaces in `wit/` +- Changes to `Cargo.toml` (version, features, dependencies) + +### Mapping: code location → doc file + +| Code path | Doc file | +| --------------------------------------------- | --------------------- | +| `src/lib.rs` (Body, Error, send_request) | `docs/SDK_API.md` | +| `derive/src/lib.rs` (handler macros) | `docs/SDK_API.md` | +| `src/http_client.rs` (outbound HTTP) | `docs/SDK_API.md` | +| `src/proxywasm/key_value.rs` | `docs/HOST_SERVICES.md` | +| `src/proxywasm/secret.rs` | `docs/HOST_SERVICES.md` | +| `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` | +| `Cargo.toml` (version, features) | `docs/INDEX.md` | +| `fastedge-plugin-source/manifest.json` | `.github/copilot-instructions.md` | + +### Violation example + +> PR changes `send_request` signature in `src/lib.rs` but `docs/SDK_API.md` still shows the old signature → **request changes**. Run `./fastedge-plugin-source/generate-docs.sh` before merge. + +### Quickstart protection + +If any public API signature or behavior changes, check whether `docs/quickstart.md` examples are still accurate. Request regeneration if examples would no longer work against the updated code. + +## Pipeline source contract + +If `fastedge-plugin-source/manifest.json` lists source files that overlap with files changed in this PR, request that `docs/` is regenerated (run `./fastedge-plugin-source/generate-docs.sh`) to keep the plugin pipeline's source material current. + +## Quality Rules + +- All public function signatures in docs must match actual source declarations +- No `eprintln!` or `eprint!` in any code or examples — output is lost on the platform +- New HTTP examples must use `#[wstd::http_server]`, not `#[fastedge::http]` +- No marketing language in documentation — precise, technical prose only diff --git a/.github/workflows/copilot-sync.yml b/.github/workflows/copilot-sync.yml new file mode 100644 index 0000000..521f5ea --- /dev/null +++ b/.github/workflows/copilot-sync.yml @@ -0,0 +1,22 @@ +name: Copilot Instructions Sync + +on: + pull_request: + paths: + - fastedge-plugin-source/manifest.json + - fastedge-plugin-source/check-copilot-sync.sh + - .github/copilot-instructions.md + - .github/workflows/copilot-sync.yml + - examples/** + - docs/** + +jobs: + check-sync: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Check manifest ↔ copilot-instructions sync + run: bash fastedge-plugin-source/check-copilot-sync.sh diff --git a/examples/cdn/cors/src/lib.rs b/examples/cdn/cors/src/lib.rs index 6abdc94..b056f7c 100644 --- a/examples/cdn/cors/src/lib.rs +++ b/examples/cdn/cors/src/lib.rs @@ -8,9 +8,10 @@ Handles preflight OPTIONS requests and adds CORS response headers for allowed origins. Supports configurable origin allow-lists, methods, and exposed headers. -Required configuration: +Configuration: - Environment variable: ALLOWED_ORIGINS (comma-separated origins or "*") -Optional configuration: + When unset or empty the filter is dormant — requests pass through + without CORS headers, so browsers will block cross-origin access. - Environment variable: ALLOWED_METHODS (default: "GET, POST, PUT, DELETE, OPTIONS") - Environment variable: MAX_AGE (default: "86400") - Environment variable: EXPOSE_HEADERS (response headers to expose) diff --git a/examples/cdn/custom_error_pages/src/lib.rs b/examples/cdn/custom_error_pages/src/lib.rs index 88e61e3..85d64d7 100644 --- a/examples/cdn/custom_error_pages/src/lib.rs +++ b/examples/cdn/custom_error_pages/src/lib.rs @@ -48,7 +48,7 @@ impl HttpContext for HttpBody { Action::Continue } - fn on_http_response_body(&mut self, _body_size: usize, end_of_stream: bool) -> Action { + fn on_http_response_body(&mut self, body_size: usize, end_of_stream: bool) -> Action { // only process 4xx/5xx error responses let Some(status) = self.get_property(vec!["response.status"]) else { return Action::Continue; @@ -139,7 +139,7 @@ impl HttpContext for HttpBody { let html_body = handlebars.render("error_template", &page_data).unwrap(); let body = html_body.as_bytes(); - self.set_http_response_body(0, body.len(), body); + self.set_http_response_body(0, body_size, body); Action::Continue } diff --git a/examples/cdn/md2html/src/lib.rs b/examples/cdn/md2html/src/lib.rs index dfdaa12..d661e10 100644 --- a/examples/cdn/md2html/src/lib.rs +++ b/examples/cdn/md2html/src/lib.rs @@ -97,7 +97,7 @@ impl HttpContext for HttpBody { html.push_str(""); let body = html.as_bytes(); - self.set_http_response_body(0, body.len(), body); + self.set_http_response_body(0, body_size, body); println!("Converted"); Action::Continue diff --git a/fastedge-plugin-source/check-copilot-sync.sh b/fastedge-plugin-source/check-copilot-sync.sh index 28d1153..0a24568 100755 --- a/fastedge-plugin-source/check-copilot-sync.sh +++ b/fastedge-plugin-source/check-copilot-sync.sh @@ -2,12 +2,13 @@ # Validates copilot-instructions.md stays in sync with the codebase: # 1. All doc files in manifest.json are referenced in the mapping table # 2. All doc files in the mapping table actually exist on disk +# 3. All example directories on disk are tracked in manifest.json # # This script is part of the fastedge-plugin pipeline contract. # Canonical template: fastedge-plugin/scripts/sync/templates/check-copilot-sync-template.sh # Each source repo gets a copy at: fastedge-plugin-source/check-copilot-sync.sh # -# Exits 0 if in sync, 1 if drift detected. +# Exits 0 if in sync (warnings don't affect exit code), 1 if drift detected. set -euo pipefail @@ -23,7 +24,7 @@ fi # --- Check 1: manifest doc files appear in the mapping table --- # Extract doc/schema paths that appear in mapping table rows (lines starting with '|') -# Use POSIX-compatible awk instead of grep -P so this works on macOS/BSD grep too +# Uses awk to parse backticked paths — works on macOS/BSD and Linux mapping_table_docs=$(awk ' /^\|/ { line = $0 @@ -49,7 +50,7 @@ if [ -f "$MANIFEST" ]; then missing=() for doc in $doc_files; do - if ! echo "$mapping_table_docs" | grep -qF "$doc"; then + if ! printf '%s\n' "$mapping_table_docs" | grep -qxF "$doc"; then missing+=("$doc") fi done @@ -88,6 +89,48 @@ else echo "OK: All doc files in copilot-instructions mapping table exist on disk" fi +# --- Check 3: example directories on disk are tracked in manifest --- +# +# Detects example projects not listed in manifest.json so the fastedge-plugin +# pipeline doesn't silently miss new examples. Uses ::warning:: annotations +# for visibility in GitHub PR checks. +# +# This check is advisory (does not affect exit code) because repos may have +# known gaps during rollout. + +if [ -d "examples" ] && [ -f "$MANIFEST" ]; then + # Get all examples/ file paths from the manifest + manifest_examples=$(jq -r '.sources[].files[]' "$MANIFEST" | grep '^examples/' | sort -u) + + # Find example project directories (contain package.json, Cargo.toml, or asconfig.json) + # Handles flat (examples//) and nested (examples/cdn//) structures + untracked=() + while IFS= read -r marker_file; do + [ -z "$marker_file" ] && continue + project_dir=$(dirname "$marker_file") + # Check if any manifest file starts with this project directory + if [ -z "$manifest_examples" ] || ! printf '%s\n' "$manifest_examples" | grep -q "^${project_dir}/"; then + untracked+=("$project_dir") + fi + done < <(find examples/ -maxdepth 4 \( -name "package.json" -o -name "Cargo.toml" -o -name "asconfig.json" \) -not -path "*/node_modules/*" 2>/dev/null | sort) + + if [ ${#untracked[@]} -gt 0 ]; then + echo "WARN: Example directories not tracked in $MANIFEST:" + for dir in "${untracked[@]}"; do + echo " - $dir" + echo "::warning::Example directory '$dir' is not tracked in manifest.json. Add it to fastedge-plugin-source/manifest.json so the fastedge-plugin pipeline can access it." + done + echo "" + echo " To fix: add source entries for the above directories to $MANIFEST" + else + echo "OK: All example directories are tracked in manifest.json" + fi +elif [ ! -d "examples" ]; then + echo "SKIP: No examples/ directory" +else + echo "SKIP: No manifest found at $MANIFEST (cannot check examples coverage)" +fi + # --- Result --- if [ $errors -ne 0 ]; then