diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml new file mode 100644 index 0000000..1b1fab8 --- /dev/null +++ b/.github/workflows/python-ci.yml @@ -0,0 +1,39 @@ +name: Python CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set Up Rust Toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: '1.86.0' + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install uv + run: pip install uv + - name: Install dependencies + run: uv sync + - name: Install viceroy + run: cargo install --git https://github.com/fastly/Viceroy.git --branch sunfishcode/sync-wit viceroy + - name: Build WebAssembly component + run: make app.wasm + - name: Install dependencies + run: uv sync --extra dev --extra test + - name: Check formatting + run: make format-check + - name: Run linting + run: make lint + - name: Run tests + run: make test diff --git a/.gitignore b/.gitignore index 407389a..19f7e6d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,6 @@ *.egg-info uv.lock -/hello_compute/ +/stubs/ __pycache__ app.wasm diff --git a/Makefile b/Makefile index c8499b7..260849f 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,28 @@ all: app.wasm +STUBS_DIR := stubs + app.wasm: wit/viceroy.wit wit/deps/fastly/compute.wit app.py - rm -rf hello_compute - uv run componentize-py -d wit -w compute bindings hello_compute - uv run componentize-py -d wit -w compute componentize --stub-wasi app -o app.wasm + rm -rf ${STUBS_DIR} + uv run componentize-py -d wit -w viceroy bindings ${STUBS_DIR} + uv run componentize-py -d wit -w viceroy componentize app -o app.wasm serve: app.wasm - viceroy --adapt app.wasm + viceroy serve app.wasm + +test: app.wasm + uv run --extra test pytest + +lint: + uv run --extra dev ruff check . + +lint-fix: + uv run --extra dev ruff check --fix . + +format: + uv run --extra dev ruff format . + +format-check: + uv run --extra dev ruff format --check . -.PHONY: all serve +.PHONY: all serve test lint format format-check diff --git a/README.md b/README.md index 085ad81..ca4eaf0 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,6 @@ Currently, this demonstrates… native-code compression stdlibs which haven't been compiled against WASI yet. Moving componentize-py to a new Python may help, as [WASIp1 is now a Tier 2 supported platform](https://peps.python.org/pep-0011/#tier-2). -* It crashes every time something tries to write to stdout or stderr. It may be - that those aren't in the preopens; adding those to the preopens should be - possible with changes to Viceroy. We're also using `--stub-wasi` at the - moment, which means things like `fd_write` are coded to immediately trap; that - probably doesn't help. Finally, it may be possible to monkeypatch in Python - and redirect them to a logging endpoint, but our initial attempts were - unsuccessful. # Install Dependencies @@ -40,7 +33,33 @@ Currently, this demonstrates… # Build and Run 1. `make serve` -2. Visit http://127.0.0.1:7676/hello/fred in a browser. +2. Visit http://127.0.0.1:7676/hello/world or http://127.0.0.1:7676/info in a browser. You are seeing Bottle, a simple Python web framework, run on a Fastly Compute worker! + +# Testing + +```bash +# Install dependencies and run tests +uv sync --extra dev --extra test +make test +``` + +The tests automatically build the WebAssembly component, start viceroy, and verify all endpoints work correctly with the WIT APIs. + +# Development + +```bash +# Format code +make format + +# Check formatting +make format-check + +# Run linting +make lint + +# Run linting and apply automatic fixes +make lint-fix +``` diff --git a/app.py b/app.py index 20a5846..d564a76 100644 --- a/app.py +++ b/app.py @@ -1,52 +1,49 @@ +import sys from urllib.parse import urlparse -import art -import bottle -from bottle import template, Bottle - -from wit_world.exports import Reactor as BaseReactor -from wit_world.imports import log, http_req, http_resp, http_body +from bottle import Bottle +from wit_world.exports import HttpIncoming as BaseHttpIncoming +from wit_world.imports import compute_runtime, http_body, http_resp from wit_world.imports.http_resp import send_downstream - # Enable a bit more debug logging from the framework. -bottle.debug(True) - app = Bottle() -app.catchall = False # bottle backtrace causes issues; use our own. @app.route("/hello/") -def index(name): - return template("Hello
{{name}}
", name=art.text2art(name)) +def hello(name): + return f"Hello {name}!" -def print(*args): - # hack to allow print locally; so far, monkeypatching - # sys.stdout/sys.stderr hasn't panned out, so more - # research required. - log.write(log_ep, " ".join(args).encode()) +@app.route("/info") +def info(): + """Return JSON with request information we can test against""" + from bottle import request + # Get some runtime info we can test + vcpu_time = compute_runtime.get_vcpu_ms() -def init(): - global log_ep - log_ep = log.endpoint_get("") + return { + "service": "fastly-compute-python", + "status": "ok", + "message": "Hello from Fastly Compute!", + "vcpu_time_ms": vcpu_time, + "request_method": request.environ.get("REQUEST_METHOD"), + "path_info": request.environ.get("PATH_INFO"), + } -class StdErr: - """File-like object to receive errors and direct them to our logging endpoint""" - - def write(self, data: str): - print(f"wsgi-error: {data}") - def flush(self): - pass +@app.route("/error") +def error(): + """Endpoint that intentionally raises an exception to test error handling.""" + raise RuntimeError("This is an intentional error for testing purposes") def serve_wsgi_request(req, body, app): """Pass a WSGI application a single request, and adapt its behavior back to the Fastly API.""" - response = http_resp.new() + response = http_resp.Response.new() response_body = http_body.new() def write(body_data: bytes): @@ -56,56 +53,27 @@ def write(body_data: bytes): def start_response(status: str, headers: list[tuple], exc_info=None): code, _description = status.split(" ", 1) - http_resp.status_set(response, int(code)) + response.set_status(int(code)) for header, value in headers: - http_resp.header_append(response, header.encode(), value.encode()) + response.append_header(header, value.encode()) return write - url = urlparse(http_req.uri_get(req, 2048)) + url = urlparse(req.get_uri(2048)) environ = { - "REQUEST_METHOD": http_req.method_get(req, 12), + "REQUEST_METHOD": req.get_method(12), "PATH_INFO": url.path, "QUERY_STRING": url.query, "SERVER_NAME": url.hostname, "SERVER_PORT": str(url.port), - "wsgi.errors": StdErr(), + "wsgi.errors": sys.stderr, } for body_chunk in app(environ, start_response): # TODO: this would be a good place to stream, but for now we just # write to the buffer and send once the handler is done. write(body_chunk) - send_downstream(response, response_body, False) - - -class Reactor(BaseReactor): - def serve(self, req: int, body: int) -> None: - init() - try: - serve_wsgi_request(req, body, app) - except Exception as e: - log_exception(e) - - -def log_exception(e): - """Pretty-print an exception to our logging endpoint. - - Do it without callling format_exc(), which calls stat() to determine whether - we're in a tty and what its width is. stat() and other fd routines currently - crash when they try to access stdout or stderr, probably because they are - not in the preopens. - """ - try: - print(f"Exception {type(e).__name__} - {e}") - print("--- Traceback Follows ---") - - current_tb = e.__traceback__ - while current_tb: - frame = current_tb.tb_frame - print( - f" File: {frame.f_code.co_filename}, " - f"Function: {frame.f_code.co_name}, " - f"Line: {frame.f_lineno}" - ) - current_tb = current_tb.tb_next - except Exception as e2: - print(f"print_exc failed {e2}") + send_downstream(response, response_body) + + +class HttpIncoming(BaseHttpIncoming): + def handle(self, request, body): + serve_wsgi_request(request, body, app) diff --git a/fastly_compute/__init__.py b/fastly_compute/__init__.py new file mode 100644 index 0000000..84534fc --- /dev/null +++ b/fastly_compute/__init__.py @@ -0,0 +1,7 @@ +"""Fastly Compute SDK for Python. + +This package provides a Python SDK for building Fastly Compute services. +""" + +# Testing utilities are available but not imported by default +# Users can import them explicitly: from fastly_compute.testing import ViceroyTestBase diff --git a/fastly_compute/pytest_plugin.py b/fastly_compute/pytest_plugin.py new file mode 100644 index 0000000..16b54cf --- /dev/null +++ b/fastly_compute/pytest_plugin.py @@ -0,0 +1,31 @@ +"""Pytest plugin for automatic viceroy output on test failures.""" + +import sys + +import pytest + + +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_makereport(item, call): + """Hook to display viceroy output on test failure. + + This hook automatically displays recent viceroy server output + when any test fails, making debugging easier. + """ + outcome = yield + rep = outcome.get_result() + + # Only show output on test failures during the call phase + if rep.when == "call" and rep.failed: + # Try to get the viceroy_server fixture from the test + if hasattr(item, "funcargs") and "viceroy_server" in item.funcargs: + server = item.funcargs["viceroy_server"] + if hasattr(server, "output_lines"): + print( + f"\n=== Viceroy output for failed test: {item.name} ===", + file=sys.stderr, + ) + # Show last 15 lines of output + for line in server.output_lines[-15:]: + print(f" {line}", file=sys.stderr) + print("=== End viceroy output ===", file=sys.stderr) diff --git a/fastly_compute/testing.py b/fastly_compute/testing.py new file mode 100644 index 0000000..22164d3 --- /dev/null +++ b/fastly_compute/testing.py @@ -0,0 +1,213 @@ +"""Testing utilities for Fastly Compute tests. + +This module provides pytest fixtures and base classes for testing +Fastly Compute services with viceroy. + +To enable automatic viceroy output on test failures, add this to your conftest.py: + + pytest_plugins = ["fastly_compute.pytest_plugin"] +""" + +import socket +import subprocess +import threading +import time +from dataclasses import dataclass +from pathlib import Path + +import pytest +import requests + + +@dataclass +class ViceroyServer: + """Represents a running viceroy server instance.""" + + process: subprocess.Popen + base_url: str + output_lines: list[str] # Capture output for debugging + + +class ViceroyTestBase: + """Base class for viceroy tests. + + Provides common functionality for testing Fastly Compute services. + Inherit from this class and use the viceroy_server fixture. + + Note: This assumes your WASM file is already built. Use your build system + (e.g., Makefile) to ensure the WASM file is up to date before running tests. + + Example: + ```python + import pytest + from fastly_compute.testing import ViceroyTestBase + + class TestMyService(ViceroyTestBase): + def test_my_endpoint(self, viceroy_server): + response = self.get("/my-endpoint") + assert response.status_code == 200 + ``` + """ + + REQUEST_TIMEOUT = 10 + WASM_FILE = "app.wasm" # Override this in subclasses if needed + server: ViceroyServer = None # Will be set by the fixture + + @staticmethod + def _find_free_port() -> int: + """Find an available port on localhost.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("", 0)) + port = s.getsockname()[1] + return port + + @pytest.fixture(scope="class", autouse=True) + @classmethod + def viceroy_server(cls) -> ViceroyServer: + """Start viceroy server for the duration of the test class. + + Note: This assumes the WASM file already exists. Use your build system + to ensure it's built before running tests. + + Returns: + ViceroyServer: Server instance with process, base_url, and captured output + """ + print("Starting viceroy server...") + + # Check if WASM file exists + wasm_path = Path(cls.WASM_FILE) + if not wasm_path.exists(): + pytest.fail( + f"WASM file '{cls.WASM_FILE}' not found. Please build it first." + ) + + # Find an available port + port = cls._find_free_port() + base_url = f"http://127.0.0.1:{port}" + output_lines = [] # Capture all output for debugging + output_lock = threading.Lock() + stop_capture = threading.Event() + + # Start viceroy process + process = subprocess.Popen( + ["viceroy", "serve", cls.WASM_FILE, "--addr", f"127.0.0.1:{port}", "-v"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + + # Start background thread to continuously capture output + def capture_output_thread(): + """Continuously capture viceroy output throughout test execution.""" + while not stop_capture.is_set(): + line = process.stdout.readline() + if not line: # EOF + break + with output_lock: + output_lines.append(line.strip()) + + output_thread = threading.Thread(target=capture_output_thread, daemon=True) + output_thread.start() + + # Wait for server to be ready + timeout = 15 + start_time = time.monotonic() + server_ready = False + + while time.monotonic() - start_time < timeout: + if process.poll() is not None: + # Process died, collect output and fail + stop_capture.set() + time.sleep(0.1) # Give thread time to capture final output + with output_lock: + all_output = "\n".join(output_lines) + pytest.fail(f"Viceroy failed to start. Output:\n{all_output}") + + # Check if we've seen the "Listening on" message + with output_lock: + for line in output_lines: + if "Listening on" in line: + print(f"Server ready: {line}") + server_ready = True + break + + if server_ready: + break + + if not server_ready: + stop_capture.set() + process.terminate() + process.wait() + with output_lock: + all_output = "\n".join(output_lines) + pytest.fail( + f"Viceroy server did not start within {timeout} seconds. Output:\n{all_output}" + ) + + server = ViceroyServer( + process=process, base_url=base_url, output_lines=output_lines + ) + + # Set the server as a class attribute so methods can access it + cls.server = server + + yield server + + # Cleanup: stop output capture and terminate the process + print("Stopping viceroy server...") + stop_capture.set() + process.terminate() + try: + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + process.wait() + + def get(self, path: str, **kwargs) -> requests.Response: + """Make a GET request to the viceroy server. + + Args: + path: URL path to request + **kwargs: Additional arguments passed to requests.get() + + Returns: + requests.Response: The HTTP response + """ + timeout = kwargs.pop("timeout", self.REQUEST_TIMEOUT) + response = requests.get( + f"{self.server.base_url}{path}", timeout=timeout, **kwargs + ) + return response + + def post(self, path: str, **kwargs) -> requests.Response: + """Make a POST request to the viceroy server. + + Args: + path: URL path to request + **kwargs: Additional arguments passed to requests.post() + + Returns: + requests.Response: The HTTP response + """ + timeout = kwargs.pop("timeout", self.REQUEST_TIMEOUT) + response = requests.post( + f"{self.server.base_url}{path}", timeout=timeout, **kwargs + ) + return response + + def request(self, method: str, path: str, **kwargs) -> requests.Response: + """Make an HTTP request to the viceroy server. + + Args: + method: HTTP method (GET, POST, PUT, DELETE, etc.) + path: URL path to request + **kwargs: Additional arguments passed to requests.request() + + Returns: + requests.Response: The HTTP response + """ + timeout = kwargs.pop("timeout", self.REQUEST_TIMEOUT) + response = requests.request( + method, f"{self.server.base_url}{path}", timeout=timeout, **kwargs + ) + return response diff --git a/pyproject.toml b/pyproject.toml index 23cf320..317ac56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,64 @@ [project] -name = "compute-sdk-python" +name = "fastly-compute" version = "0.1.0" description = "Python SDK for Fastly Compute" readme = "README.md" requires-python = ">=3.12" dependencies = [ - "art", "bottle", "componentize-py", ] + +[project.optional-dependencies] +test = [ + "pytest (>=8.4.0,<9.0.0)", + "requests (>=2.32.5,<3.0.0)", +] +dev = [ + "ruff (>=0.12.11,<0.13.0)", +] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +addopts = [ + "-v", + "--tb=short", + "--strict-markers", +] + +[tool.ruff] +target-version = "py312" +line-length = 88 + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "UP", # pyupgrade +] +ignore = [ + "E501", # line too long, handled by formatter +] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" + +[build-system] +requires = [ + "setuptools (>=80.9.0,<81.0.0)", + "wheel (>=0.45.0,<0.46.0)" +] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +py-modules = ["app"] diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..0df0cc7 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,49 @@ +# Fastly Compute SDK Tests + +Tests for Fastly Compute SDK using viceroy with automatic +server management, dynamic port allocation, and comprehensive error handling. + +## Quick Start + +```python +import pytest +from fastly_compute.testing import ViceroyTestBase + +class TestMyService(ViceroyTestBase): + def test_endpoint(self): + response = self.get("/test") + assert response.status_code == 200 +``` + +**Prerequisites**: WASM file must exist (handled by your build system). + +## Available Methods + +- `self.get(path, **kwargs)` - GET request +- `self.post(path, **kwargs)` - POST request +- `self.request(method, path, **kwargs)` - Any HTTP method + +## Configuration + +```python +class TestMyService(ViceroyTestBase): + REQUEST_TIMEOUT = 30 # Custom timeout (default: 10s) + WASM_FILE = "my-service.wasm" # Custom WASM file (default: "app.wasm") +``` + +## Running Tests + +```bash +make test # Build and run tests +uv run pytest -v -s # Verbose output with viceroy logs +``` + +### Enabling Automatic Viceroy Output + +To get viceroy logs automatically displayed on test failures, add this to your `conftest.py`: + +```python +pytest_plugins = ["fastly_compute.pytest_plugin"] +``` + +This enables automatic display of recent viceroy server output whenever a test fails, making debugging much easier. diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..6ad7227 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Test package for Fastly Compute tests.""" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..748fac2 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,4 @@ +"""Pytest configuration for Fastly Compute tests.""" + +# Enable the fastly_compute pytest plugin for automatic viceroy output on failures +pytest_plugins = ["fastly_compute.pytest_plugin"] diff --git a/tests/test_app.py b/tests/test_app.py new file mode 100644 index 0000000..0bf4d30 --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,69 @@ +"""Tests for the Fastly Compute Python service (app.wasm functionality).""" + +from fastly_compute.testing import ViceroyTestBase + + +class TestFastlyComputeApp(ViceroyTestBase): + def test_hello_endpoint(self): + """Test the hello endpoint returns expected content.""" + response = self.get("/hello/test") + + assert response.status_code == 200 + assert response.text == "Hello test!" + + def test_hello_endpoint_with_different_name(self): + """Test the hello endpoint with a different name parameter.""" + response = self.get("/hello/world") + + assert response.status_code == 200 + assert response.text == "Hello world!" + + def test_info_endpoint(self): + """Test the info endpoint returns expected JSON with WIT data.""" + response = self.get("/info") + + assert response.status_code == 200 + assert response.headers.get("content-type", "").startswith("application/json") + + data = response.json() + + # Check basic service info + assert data["service"] == "fastly-compute-python" + assert data["status"] == "ok" + assert "message" in data + + # Check WIT API data + assert "vcpu_time_ms" in data + assert isinstance(data["vcpu_time_ms"], int) + + # Check request data + assert data["request_method"] == "GET" + assert data["path_info"] == "/info" + + def test_nonexistent_endpoint(self): + """Test that nonexistent endpoints return 404.""" + response = self.get("/nonexistent") + + assert response.status_code == 404 + + def test_post_request_handling(self): + """Test that POST requests are handled correctly.""" + # Current app.py doesn't handle POST to /api/data, so expect 404 + response = self.post("/api/data", json={"key": "value"}) + assert response.status_code == 404 + + def test_custom_headers(self): + """Test requests with custom headers are processed.""" + headers = {"X-Custom-Header": "test-value"} + response = self.get("/info", headers=headers) + assert response.status_code == 200 + + def test_error_endpoint_handling(self): + """Test that the error endpoint returns 500 and triggers viceroy output display.""" + response = self.get("/error") + + # The endpoint should return a 500 error due to the exception + assert response.status_code == 500 + + # This test also serves to verify that the built-in hook works + # If this test fails, we should see viceroy output in the test results diff --git a/tests/test_testing.py b/tests/test_testing.py new file mode 100644 index 0000000..669b1fb --- /dev/null +++ b/tests/test_testing.py @@ -0,0 +1,76 @@ +"""Tests for the viceroy testing framework functionality.""" + +import pytest +import requests + +from fastly_compute.testing import ViceroyTestBase + + +class TestViceroyTestingFramework(ViceroyTestBase): + """Tests that verify the testing framework itself works correctly.""" + + def test_viceroy_server_fixture_provides_server_info(self): + """Test that the viceroy_server fixture provides expected attributes.""" + # Check that the fixture sets up a ViceroyServer with expected attributes + assert hasattr(self.server, "process") + assert hasattr(self.server, "base_url") + assert hasattr(self.server, "output_lines") + + # Check that base_url is properly formatted + assert self.server.base_url.startswith("http://127.0.0.1:") + + # Check that output_lines contains viceroy startup output + assert len(self.server.output_lines) > 0 + listening_lines = [ + line for line in self.server.output_lines if "Listening on" in line + ] + assert len(listening_lines) > 0 + + def test_get_method_works(self): + """Test that the get() helper method works correctly.""" + response = self.get("/info") + + # Verify it returns a requests.Response object + assert isinstance(response, requests.Response) + assert response.status_code == 200 + + def test_post_method_works(self): + """Test that the post() helper method works correctly.""" + response = self.post("/nonexistent", json={"test": "data"}) + + # Verify it returns a requests.Response object + assert isinstance(response, requests.Response) + # POST to nonexistent endpoint should return 404 + assert response.status_code == 404 + + def test_request_method_works(self): + """Test that the request() helper method works correctly.""" + response = self.request("GET", "/info") + + # Verify it returns a requests.Response object + assert isinstance(response, requests.Response) + assert response.status_code == 200 + + def test_request_timeout_handling(self): + """Test that request timeouts work correctly.""" + # Test that normal requests work with reasonable timeout + response = self.get("/info", timeout=5.0) + assert response.status_code == 200 + + # Test that very short timeouts raise TimeoutError + with pytest.raises(requests.exceptions.ReadTimeout): + self.get("/info", timeout=0.001) + + def test_custom_request_timeout_setting(self): + """Test that custom REQUEST_TIMEOUT class attribute works.""" + # Temporarily change the timeout + original_timeout = self.REQUEST_TIMEOUT + self.REQUEST_TIMEOUT = 1 + + try: + # This should work with 1 second timeout + response = self.get("/info") + assert response.status_code == 200 + finally: + # Restore original timeout + self.REQUEST_TIMEOUT = original_timeout diff --git a/wit/deps/cli/command.wit b/wit/deps/cli/command.wit index d8005bd..6d3cc83 100644 --- a/wit/deps/cli/command.wit +++ b/wit/deps/cli/command.wit @@ -1,7 +1,10 @@ -package wasi:cli@0.2.0; +package wasi:cli@0.2.6; +@since(version = 0.2.0) world command { + @since(version = 0.2.0) include imports; + @since(version = 0.2.0) export run; } diff --git a/wit/deps/cli/environment.wit b/wit/deps/cli/environment.wit index 7006523..2f449bd 100644 --- a/wit/deps/cli/environment.wit +++ b/wit/deps/cli/environment.wit @@ -1,3 +1,4 @@ +@since(version = 0.2.0) interface environment { /// Get the POSIX-style environment variables. /// @@ -7,12 +8,15 @@ interface environment { /// Morally, these are a value import, but until value imports are available /// in the component model, this import function should return the same /// values each time it is called. + @since(version = 0.2.0) get-environment: func() -> list>; /// Get the POSIX-style arguments to the program. + @since(version = 0.2.0) get-arguments: func() -> list; /// Return a path that programs should use as their initial current working /// directory, interpreting `.` as shorthand for this. + @since(version = 0.2.0) initial-cwd: func() -> option; } diff --git a/wit/deps/cli/exit.wit b/wit/deps/cli/exit.wit index d0c2b82..427935c 100644 --- a/wit/deps/cli/exit.wit +++ b/wit/deps/cli/exit.wit @@ -1,4 +1,17 @@ +@since(version = 0.2.0) interface exit { /// Exit the current instance and any linked instances. + @since(version = 0.2.0) exit: func(status: result); + + /// Exit the current instance and any linked instances, reporting the + /// specified status code to the host. + /// + /// The meaning of the code depends on the context, with 0 usually meaning + /// "success", and other values indicating various types of failure. + /// + /// This function does not return; the effect is analogous to a trap, but + /// without the connotation that something bad has happened. + @unstable(feature = cli-exit-with-code) + exit-with-code: func(status-code: u8); } diff --git a/wit/deps/cli/imports.wit b/wit/deps/cli/imports.wit index 083b84a..d9fd017 100644 --- a/wit/deps/cli/imports.wit +++ b/wit/deps/cli/imports.wit @@ -1,20 +1,36 @@ -package wasi:cli@0.2.0; +package wasi:cli@0.2.6; +@since(version = 0.2.0) world imports { - include wasi:clocks/imports@0.2.0; - include wasi:filesystem/imports@0.2.0; - include wasi:sockets/imports@0.2.0; - include wasi:random/imports@0.2.0; - include wasi:io/imports@0.2.0; + @since(version = 0.2.0) + include wasi:clocks/imports@0.2.6; + @since(version = 0.2.0) + include wasi:filesystem/imports@0.2.6; + @since(version = 0.2.0) + include wasi:sockets/imports@0.2.6; + @since(version = 0.2.0) + include wasi:random/imports@0.2.6; + @since(version = 0.2.0) + include wasi:io/imports@0.2.6; + @since(version = 0.2.0) import environment; + @since(version = 0.2.0) import exit; + @since(version = 0.2.0) import stdin; + @since(version = 0.2.0) import stdout; + @since(version = 0.2.0) import stderr; + @since(version = 0.2.0) import terminal-input; + @since(version = 0.2.0) import terminal-output; + @since(version = 0.2.0) import terminal-stdin; + @since(version = 0.2.0) import terminal-stdout; + @since(version = 0.2.0) import terminal-stderr; } diff --git a/wit/deps/cli/run.wit b/wit/deps/cli/run.wit index a70ee8c..655346e 100644 --- a/wit/deps/cli/run.wit +++ b/wit/deps/cli/run.wit @@ -1,4 +1,6 @@ +@since(version = 0.2.0) interface run { /// Run the program. + @since(version = 0.2.0) run: func() -> result; } diff --git a/wit/deps/cli/stdio.wit b/wit/deps/cli/stdio.wit index 31ef35b..cb8aea2 100644 --- a/wit/deps/cli/stdio.wit +++ b/wit/deps/cli/stdio.wit @@ -1,17 +1,26 @@ +@since(version = 0.2.0) interface stdin { - use wasi:io/streams@0.2.0.{input-stream}; + @since(version = 0.2.0) + use wasi:io/streams@0.2.6.{input-stream}; + @since(version = 0.2.0) get-stdin: func() -> input-stream; } +@since(version = 0.2.0) interface stdout { - use wasi:io/streams@0.2.0.{output-stream}; + @since(version = 0.2.0) + use wasi:io/streams@0.2.6.{output-stream}; + @since(version = 0.2.0) get-stdout: func() -> output-stream; } +@since(version = 0.2.0) interface stderr { - use wasi:io/streams@0.2.0.{output-stream}; + @since(version = 0.2.0) + use wasi:io/streams@0.2.6.{output-stream}; + @since(version = 0.2.0) get-stderr: func() -> output-stream; } diff --git a/wit/deps/cli/terminal.wit b/wit/deps/cli/terminal.wit index 38c724e..d305498 100644 --- a/wit/deps/cli/terminal.wit +++ b/wit/deps/cli/terminal.wit @@ -3,8 +3,10 @@ /// In the future, this may include functions for disabling echoing, /// disabling input buffering so that keyboard events are sent through /// immediately, querying supported features, and so on. +@since(version = 0.2.0) interface terminal-input { /// The input side of a terminal. + @since(version = 0.2.0) resource terminal-input; } @@ -13,37 +15,48 @@ interface terminal-input { /// In the future, this may include functions for querying the terminal /// size, being notified of terminal size changes, querying supported /// features, and so on. +@since(version = 0.2.0) interface terminal-output { /// The output side of a terminal. + @since(version = 0.2.0) resource terminal-output; } /// An interface providing an optional `terminal-input` for stdin as a /// link-time authority. +@since(version = 0.2.0) interface terminal-stdin { + @since(version = 0.2.0) use terminal-input.{terminal-input}; /// If stdin is connected to a terminal, return a `terminal-input` handle /// allowing further interaction with it. + @since(version = 0.2.0) get-terminal-stdin: func() -> option; } /// An interface providing an optional `terminal-output` for stdout as a /// link-time authority. +@since(version = 0.2.0) interface terminal-stdout { + @since(version = 0.2.0) use terminal-output.{terminal-output}; /// If stdout is connected to a terminal, return a `terminal-output` handle /// allowing further interaction with it. + @since(version = 0.2.0) get-terminal-stdout: func() -> option; } /// An interface providing an optional `terminal-output` for stderr as a /// link-time authority. +@since(version = 0.2.0) interface terminal-stderr { + @since(version = 0.2.0) use terminal-output.{terminal-output}; /// If stderr is connected to a terminal, return a `terminal-output` handle /// allowing further interaction with it. + @since(version = 0.2.0) get-terminal-stderr: func() -> option; } diff --git a/wit/deps/clocks/monotonic-clock.wit b/wit/deps/clocks/monotonic-clock.wit index 4e4dc3a..f3bc839 100644 --- a/wit/deps/clocks/monotonic-clock.wit +++ b/wit/deps/clocks/monotonic-clock.wit @@ -1,4 +1,4 @@ -package wasi:clocks@0.2.0; +package wasi:clocks@0.2.6; /// WASI Monotonic Clock is a clock API intended to let users measure elapsed /// time. /// @@ -7,38 +7,43 @@ package wasi:clocks@0.2.0; /// /// A monotonic clock is a clock which has an unspecified initial value, and /// successive reads of the clock will produce non-decreasing values. -/// -/// It is intended for measuring elapsed time. +@since(version = 0.2.0) interface monotonic-clock { - use wasi:io/poll@0.2.0.{pollable}; + @since(version = 0.2.0) + use wasi:io/poll@0.2.6.{pollable}; /// An instant in time, in nanoseconds. An instant is relative to an /// unspecified initial value, and can only be compared to instances from /// the same monotonic-clock. + @since(version = 0.2.0) type instant = u64; /// A duration of time, in nanoseconds. + @since(version = 0.2.0) type duration = u64; /// Read the current value of the clock. /// /// The clock is monotonic, therefore calling this function repeatedly will /// produce a sequence of non-decreasing values. + @since(version = 0.2.0) now: func() -> instant; /// Query the resolution of the clock. Returns the duration of time /// corresponding to a clock tick. + @since(version = 0.2.0) resolution: func() -> duration; /// Create a `pollable` which will resolve once the specified instant - /// occured. + /// has occurred. + @since(version = 0.2.0) subscribe-instant: func( when: instant, ) -> pollable; - /// Create a `pollable` which will resolve once the given duration has - /// elapsed, starting at the time at which this function was called. - /// occured. + /// Create a `pollable` that will resolve after the specified duration has + /// elapsed from the time this function is invoked. + @since(version = 0.2.0) subscribe-duration: func( when: duration, ) -> pollable; diff --git a/wit/deps/clocks/timezone.wit b/wit/deps/clocks/timezone.wit new file mode 100644 index 0000000..ca98ad1 --- /dev/null +++ b/wit/deps/clocks/timezone.wit @@ -0,0 +1,55 @@ +package wasi:clocks@0.2.6; + +@unstable(feature = clocks-timezone) +interface timezone { + @unstable(feature = clocks-timezone) + use wall-clock.{datetime}; + + /// Return information needed to display the given `datetime`. This includes + /// the UTC offset, the time zone name, and a flag indicating whether + /// daylight saving time is active. + /// + /// If the timezone cannot be determined for the given `datetime`, return a + /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight + /// saving time. + @unstable(feature = clocks-timezone) + display: func(when: datetime) -> timezone-display; + + /// The same as `display`, but only return the UTC offset. + @unstable(feature = clocks-timezone) + utc-offset: func(when: datetime) -> s32; + + /// Information useful for displaying the timezone of a specific `datetime`. + /// + /// This information may vary within a single `timezone` to reflect daylight + /// saving time adjustments. + @unstable(feature = clocks-timezone) + record timezone-display { + /// The number of seconds difference between UTC time and the local + /// time of the timezone. + /// + /// The returned value will always be less than 86400 which is the + /// number of seconds in a day (24*60*60). + /// + /// In implementations that do not expose an actual time zone, this + /// should return 0. + utc-offset: s32, + + /// The abbreviated name of the timezone to display to a user. The name + /// `UTC` indicates Coordinated Universal Time. Otherwise, this should + /// reference local standards for the name of the time zone. + /// + /// In implementations that do not expose an actual time zone, this + /// should be the string `UTC`. + /// + /// In time zones that do not have an applicable name, a formatted + /// representation of the UTC offset may be returned, such as `-04:00`. + name: string, + + /// Whether daylight saving time is active. + /// + /// In implementations that do not expose an actual time zone, this + /// should return false. + in-daylight-saving-time: bool, + } +} diff --git a/wit/deps/clocks/wall-clock.wit b/wit/deps/clocks/wall-clock.wit index 440ca0f..76636a0 100644 --- a/wit/deps/clocks/wall-clock.wit +++ b/wit/deps/clocks/wall-clock.wit @@ -1,4 +1,4 @@ -package wasi:clocks@0.2.0; +package wasi:clocks@0.2.6; /// WASI Wall Clock is a clock API intended to let users query the current /// time. The name "wall" makes an analogy to a "clock on the wall", which /// is not necessarily monotonic as it may be reset. @@ -13,8 +13,10 @@ package wasi:clocks@0.2.0; /// monotonic, making it unsuitable for measuring elapsed time. /// /// It is intended for reporting the current date and time for humans. +@since(version = 0.2.0) interface wall-clock { /// A time and date in seconds plus nanoseconds. + @since(version = 0.2.0) record datetime { seconds: u64, nanoseconds: u32, @@ -33,10 +35,12 @@ interface wall-clock { /// /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + @since(version = 0.2.0) now: func() -> datetime; /// Query the resolution of the clock. /// /// The nanoseconds field of the output is always less than 1000000000. + @since(version = 0.2.0) resolution: func() -> datetime; } diff --git a/wit/deps/clocks/world.wit b/wit/deps/clocks/world.wit index c022457..5c53c51 100644 --- a/wit/deps/clocks/world.wit +++ b/wit/deps/clocks/world.wit @@ -1,6 +1,11 @@ -package wasi:clocks@0.2.0; +package wasi:clocks@0.2.6; +@since(version = 0.2.0) world imports { + @since(version = 0.2.0) import monotonic-clock; + @since(version = 0.2.0) import wall-clock; + @unstable(feature = clocks-timezone) + import timezone; } diff --git a/wit/deps/fastly-adapter/adapter.wit b/wit/deps/fastly-adapter/adapter.wit new file mode 100644 index 0000000..4ceadf3 --- /dev/null +++ b/wit/deps/fastly-adapter/adapter.wit @@ -0,0 +1,112 @@ +/// Interfaces available to the component adapter, which are not otherwise +/// part of the Fastly Compute platform. +package fastly:adapter; + +/// Adapter functions formerly of `fastly:compute/http-req`. +/// +/// These functions depend on the host maintaining an implicit downstream +/// request. They were deprecated and replaced by functions in the +/// `http-downstream` interface which do the same thing but take an explicit +/// `request` handle. +/// +/// We could almost polyfill these functions in the adapter, by having the +/// adapter remember the downstream request handle passed in and calling the +/// `http-downstream` versions with it, but not quite. Guest programs can call +/// `send` and pass it the downstream handle, which consumes the downstream +/// handle. If guest programs do that and later call one of these functions, +/// the polyfill no longer has a valid handle it can pass in. +/// +/// So instead, we moved them to be private functions, still implemented by +/// the host, and still accessible through the component adapter, but not +/// accessible to public Wit users. +interface adapter-http-req { + use fastly:compute/types.{error, ip-address}; + use fastly:compute/http-req.{client-cert-verify-result}; + + downstream-client-ip-addr: func() -> option; + downstream-server-ip-addr: func() -> option; + downstream-client-h2-fingerprint: func(max-len: u64) -> result; + downstream-client-request-id: func(max-len: u64) -> result; + downstream-client-oh-fingerprint: func(max-len: u64) -> result; + downstream-client-ddos-detected: func() -> result; + downstream-tls-cipher-openssl-name: func(max-len: u64) -> result, error>; + downstream-tls-protocol: func(max-len: u64) -> result, error>; + downstream-tls-client-hello: func(max-len: u64) -> result, error>; + downstream-tls-client-cert-verify-result: func() -> result; + downstream-tls-ja3-md5: func() -> result, error>; + downstream-tls-ja4: func(max-len: u64) -> result; + downstream-compliance-region: func(max-len: u64) -> result; + + /// Deprecated, because it doesn't return `error.optional-none` on an empty certificate. + downstream-tls-raw-client-certificate-deprecated: func(max-len: u64) -> result, error>; + + get-original-header-names: func( + max-len: u64, + cursor: u32, + ) -> result>, error>; + + original-header-count: func() -> result; + + fastly-key-is-valid: func() -> result; + + /// Deprecated; use `redirect-to-websocket-proxy` instead. + redirect-to-websocket-proxy-deprecated: func(backend: string) -> result<_, error>; + + /// Deprecated; use `redirect-to-grip-proxy` instead. + redirect-to-grip-proxy-deprecated: func(backend: string) -> result<_, error>; +} + +interface adapter-http-downstream { + use fastly:compute/types.{error}; + use fastly:compute/http-req.{request}; + + /// Deprecated, because it doesn't return `error.optional-none` on an empty certificate. + downstream-tls-raw-client-certificate-deprecated: func( + ds-request: borrow, + max-len: u64 + ) -> result, error>; +} + +/// User-agent string parsing (deprecated). +/// +/// This was public in the Witx ABI, but it was deprecated, so now it's a +/// fastly-private API, available to existing code using the adapter, but +/// not available publicly. +interface adapter-uap { + use fastly:compute/types.{error}; + + resource user-agent { + family: func(max-len: u64) -> result; + major: func(max-len: u64) -> result; + minor: func(max-len: u64) -> result; + patch: func(max-len: u64) -> result; + } + + /// Parses a user agent string. + parse: func(user-agent: list) -> result; +} + +/// A world that just imports all the deprecated APIs, split out from the main +/// world below so that we can refer to it in tests. +world adapter-imports { + import adapter-http-req; + import adapter-http-downstream; + import adapter-uap; +} + +/// The `fastly:compute/service` world plus the deprecated interfaces. +world adapter-service { + // Make this world a superset of the public `service` world. + include fastly:compute/service; + + // And, add all the deprecated interfaces. + include adapter-imports; +} + +/// Like `adapter-service`, but only includes the imports, and not the +/// exports (`http-incoming.handle`), so that it can be used by library components +/// that don't have their own `main` function. +world adapter-service-imports { + include fastly:compute/service-imports; + include adapter-imports; +} diff --git a/wit/deps/fastly/compute.wit b/wit/deps/fastly/compute.wit index 18934c7..0346749 100644 --- a/wit/deps/fastly/compute.wit +++ b/wit/deps/fastly/compute.wit @@ -1,45 +1,60 @@ -package fastly:api; +/// This is a [Wit] file defining the APIs of the [Fastly Compute platform]. +/// +/// This file defines the `fastly:compute/service` world, which defines the +/// set of interfaces available to, and expected of, Fastly Compute service +/// applications. +/// +/// [Wit]: https://component-model.bytecodealliance.org/design/wit.html +/// [Fastly Compute platform]: https://www.fastly.com/documentation/guides/compute/ +package fastly:compute; +/// Types used by many interfaces in this package. interface types { - // TODO: split this up into function-specific error enums + /// A common error type used by many functions in this package. + /// + /// TODO: In the future this should be split up into more-specific error + /// enums so that it better documents which errors each function can actually + /// return and what they mean. variant error { - /// Unknown error value. - /// It should be an internal error if this is returned. - unknown-error, /// Generic error value. - /// This means that some unexpected error occurred during a hostcall. + /// + /// This means that some unexpected error occurred. generic-error, /// Invalid argument. invalid-argument, /// Invalid handle. - /// Thrown when a handle is not valid. E.G. No dictionary exists with the given name. + /// + /// Returned when a handle is not valid, for example when no dictionary exists with the given + /// name. bad-handle, /// Buffer length error. - /// Thrown when a buffer is the wrong size. + /// + /// Returned when a buffer is the wrong size. /// Includes the buffer length that would allow the operation to succeed. buffer-len(u64), /// Unsupported operation error. - /// This error is thrown when some operation cannot be performed, because it is not supported. + /// + /// This error is returned when some operation cannot be performed, because it is not supported. unsupported, - /// Alignment error. - /// This is thrown when a pointer does not point to a properly aligned slice of memory. - bad-align, /// Invalid HTTP error. - /// This can be thrown when a method, URI, header, or status is not valid. This can also - /// be thrown if a message head is too large. + /// + /// This can be returned when a method, URI, header, or status is not valid. This can also + /// be returned if a message head is too large. http-invalid, /// HTTP user error. - /// This is thrown in cases where user code caused an HTTP error. For example, attempt to send + /// + /// This is returned in cases where user code caused an HTTP error. For example, attempt to send /// a 1xx response code, or a request with a non-absolute URI. This can also be caused by /// an unexpected header: both `content-length` and `transfer-encoding`, for example. http-user, /// HTTP incomplete message error. - /// This can be thrown when a stream ended unexpectedly. + /// + /// This can be returned when a stream ended unexpectedly. http-incomplete, - /// A `None` error. + /// A “none” error. + /// /// This status code is used to indicate when an optional value did not exist, as opposed to /// an empty value. - /// Note, this value should no longer be used, as we have explicit optional types now. optional-none, /// Message head too large. http-head-too-large, @@ -49,217 +64,666 @@ interface types { /// /// This is returned when an attempt to allocate a resource has exceeded the maximum number of /// resources permitted. For example, creating too many response handles. - limit-exceeded + limit-exceeded, } - /// A handle to an individual secret. - type secret-handle = u32; -} - -interface http-types { + /// IPv4 addresses. + type ipv4-address = tuple; - use types.{secret-handle}; + /// IPv6 addresses. + type ipv6-address = tuple; - /// A handle to an HTTP request or response body. - type body-handle = u32; + /// IPv4 or IPv6 addresses. + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } +} - /// A handle to an HTTP request. - type request-handle = u32; - /// A handle to a currently-pending asynchronous HTTP request. - type pending-request-handle = u32; - /// A handle to an HTTP response. - type response-handle = u32; - type request = tuple; - type response = tuple; +/// Types used by HTTP interfaces in this package. +interface http-types { - /// A tag indicating HTTP protocol versions. + /// HTTP protocol versions. enum http-version { + /// HTTP/0.9 http09, + /// HTTP/1.0 http10, + /// HTTP/1.1 http11, + /// HTTP/2.0 h2, + /// HTTP/3.0 h3 } + /// HTTP [content encoding] flags + /// + /// [content encoding]: https://www.rfc-editor.org/rfc/rfc9110.html#field.content-encoding flags content-encodings { + /// [Gzip coding] + /// + /// [Gzip coding]: https://www.rfc-editor.org/rfc/rfc9110.html#gzip.coding gzip } - /// Adjust how this requests's framing headers are determined. + /// Determines how the framing headers (`Content-Length`/`Transfer-Encoding`) are set for a + /// request or response. enum framing-headers-mode { + /// Determine the framing headers automatically based on the message body, and discard any + /// framing headers already set in the message. This is the default behavior. + /// + /// In automatic mode, a `Content-Length` is used when the size of the body can be determined + /// before it is sent. Requests/responses sent in streaming mode, where headers are sent + /// immediately but the content of the body is streamed later, will receive a + /// `Transfer-Encoding: chunked` to accommodate the dynamic generation of the body. automatic, + + /// Use the exact framing headers set in the message, falling back to `automatic` if invalid. + /// + /// In “from headers” mode, any `Content-Length` or `Transfer-Encoding` headers will be honored. + /// You must ensure that those headers have correct values permitted by the + /// [HTTP/1.1 specification]. If the provided headers are not permitted by the spec, the headers + /// will revert to automatic mode and a log diagnostic will be issued about what was wrong. If a + /// `Content-Length` is permitted by the spec, but the value doesn't match the size of the + /// actual body, the body will either be truncated (if it is too long), or the connection will + /// be hung up early (if it is too short). + /// + /// [HTTP/1.1 specification]: https://www.rfc-editor.org/rfc/rfc7230#section-3.3.1 manually-from-headers } + /// [Transport Layer Security] (TLS) version + /// + /// [Transport Layer Security]: https://www.rfc-editor.org/rfc/rfc8446.html enum tls-version { + /// TLS 1.0 tls1, + /// TLS 1.1 tls11, + /// TLS 1.2 tls12, + /// TLS 1.3 tls13 } - flags backend-config-options { - reserved, - host-override, - connect-timeout, - first-byte-timeout, - between-bytes-timeout, - use-tls, - tls-min-version, - tls-max-version, - cert-hostname, - ca-cert, - ciphers, - sni-hostname, - dont-pool, - client-cert, - grpc, - keepalive, - } - - /// Create a backend for later use - record dynamic-backend-config { - host-override: string, - connect-timeout: u32, - first-byte-timeout: u32, - between-bytes-timeout: u32, - tls-min-version: option, - tls-max-version: option, - cert-hostname: string, - ca-cert: string, - ciphers: string, - sni-hostname: string, - client-cert: string, - client-key: secret-handle, - http-keepalive-time-ms: u32, - tcp-keepalive-enable: u32, - tcp-keepalive-interval-secs: u32, - tcp-keepalive-probes: u32, - tcp-keepalive-time-secs: u32, - } - - /// HTTP status codes. + /// HTTP [status codes]. + /// + /// [status codes]: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml type http-status = u16; } -/// Fastly UAP -interface uap { +/// HTTP bodies. +interface http-body { use types.{error}; - resource user-agent { - family: func(max-len: u64) -> result; - major: func(max-len: u64) -> result; - minor: func(max-len: u64) -> result; - patch: func(max-len: u64) -> result; - } + /// An HTTP request or response body. + use async-io.{pollable as body}; - /// Parse a user agent string. - parse: func(user-agent: list) -> result; -} + /// Creates a new empty body that can be used for outgoing requests and responses. + new: func() -> result; -/// Fastly HTTP Body -interface http-body { + /// Appends the contents of the body `src` to the body `dest`. + append: func(dest: borrow, src: body) -> result<_, error>; - use types.{error}; - use http-types.{body-handle}; + /// Reads from a body. + read: func(body: borrow, chunk-size: u32) -> result, error>; + + /// Writes to a body. + write: func(body: borrow, buf: list, end: write-end) -> result; + /// Which side of a body to write to. enum write-end { + /// Write to the back of the body; that is, append to it. back, + + /// Write to the front of the body; that is, prepend to it. front } - append: func(dest: body-handle, src: body-handle) -> result<_, error>; - - new: func() -> result; - - read: func(h: body-handle, chunk-size: u32) -> result, error>; - - write: func(h: body-handle, buf: list, end: write-end) -> result; - - /// Frees the body on the host. + /// Frees a body. + /// + /// This releases resources associated with the body. /// - /// For streaming bodies, this is a _successful_ stream termination, which will signal + /// For streaming bodies, this is a *successful* stream termination, which will signal /// via framing that the body transfer is complete. - close: func(h: body-handle) -> result<_, error>; - - /// Frees a streaming body on the host _unsuccessfully_, so that framing makes clear that - /// the body is incomplete. - abandon: func(h: body-handle) -> result<_, error>; + /// + /// If a handle is dropped without calling `close`, it's an *unsuccessful* stream + /// termination. + close: func(body: body) -> result<_, error>; - /// Returns a u64 body length if the length of a body is known, or `FastlyStatus::None` - /// otherwise. + /// Returns a `u64` body length if the length of a body is known, or `none` otherwise. /// /// If the length is unknown, it is likely due to the body arising from an HTTP/1.1 message with /// chunked encoding, an HTTP/2 or later message with no `content-length`, or being a streaming /// body. /// - /// Note that receiving a length from this function does not guarantee that the full number of + /// Receiving a length from this function does not guarantee that the full number of /// bytes can actually be read from the body. For example, when proxying a response from a /// backend, this length may reflect the `content-length` promised in the response, but if the /// backend connection is closed prematurely, fewer bytes may be delivered before this body /// handle can no longer be read. - known-length: func(h: body-handle) -> result; + get-known-length: func(body: borrow) -> option; - trailer-append: func( - h: body-handle, - name: list, + /// Adds a body trailing header with given value. + append-trailer: func( + body: borrow, + name: string, value: list, ) -> result<_, error>; - trailer-names-get: func( - h: body-handle, + /// Gets the names of the trailers associated with this body. + /// + /// The first `cursor` names are skipped. The remaining names are encoded successively with + /// a NUL byte after each into a list of bytes at most `max-len` long. If any of the remaining + /// names don't fit, the returned `option` is the index of the first name that didn't fit, + /// or `none` if all the remaining names fit. If `max-len` is too small to fit any name, an + /// `error.buffer-len` error is returned, providing a recommended buffer size. + get-trailer-names: func( + body: borrow, max-len: u64, cursor: u32, - ) -> result, option>>, error>; + ) -> result>, error>; - trailer-value-get: func( - h: body-handle, - name: list, + /// Gets the value for the trailer with the given name, or `none` if the trailer is not present. + /// + /// If there are multiple values for this header, only one is returned, which may be + /// any of the values. See `get-trailer-values` if you need to get all of the values. + /// + /// This functions returns `ok(some(v))` if the trailer with the given name is present, + /// and `ok(none)` if no trailer with the given name is present. If `max-len` is too + /// small to fit the value, an `error.buffer-len` error is returned, providing a + /// recommended buffer size. + get-trailer-value: func( + body: borrow, + name: string, max-len: u64, ) -> result>, error>; - trailer-values-get: func( - h: body-handle, - name: list, + /// Gets multiple values associated with the trailer with the given name. + /// + /// As opposed to `get-trailer-value`, this function returns all of the values for this trailer. + /// + /// The first `cursor` values are skipped. The remaining values are encoded successively with + /// a NUL byte after each into a list of bytes at most `max-len` long. If any of the remaining + /// values don't fit, the returned `option` is the index of the first value that didn't + /// fit, or `none` if all the remaining values fit. If `max-len` is too small to fit any value, + /// an `error.buffer-len` error is returned, providing a recommended buffer size. + get-trailer-values: func( + body: borrow, + name: string, max-len: u64, cursor: u32 - ) -> result, option>>, error>; + ) -> result, option>, error>; } -/// Fastly Log +/// Low-level interface to Fastly's [Real-Time Log Streaming] endpoints. +/// +/// [Real-Time Log Streaming]: https://docs.fastly.com/en/guides/about-fastlys-realtime-log-streaming-features interface log { use types.{error}; - /// A handle to a logging endpoint. - type handle = u32; + /// A logging endpoint. + resource endpoint { + /// Tries to get an endpoint by name. + /// + /// Currently, the conditions on an endpoint name are: + /// - It must not be empty. + /// - It must not contain newlines (`\n`) or colons (`:`). + /// - It must not be `stdout` or `stderr`, which are reserved for debugging. + /// + /// Names are case sensitive. Calling `get-endpoint` with a name that doesn't correspond to any + /// logging endpoint available in your service will still return a usable endpoint, and writes + /// to that endpoint will succeed. Refer to your service dashboard to diagnose missing log + /// events. + get: static func(name: string) -> result; + + /// Writes a data to the given endpoint. + /// + /// Each call to `write` produces a single log event. On success, the number of bytes written + /// is returned. + write: func(msg: list) -> result; + } +} + +/// HTTP downstream requests and metadata. +/// +/// “Downstream” here refers to incoming HTTP requests. +interface http-downstream { + use types.{error, ip-address}; + use http-req.{ + request, client-cert-verify-result, error-with-detail, cache-override, request-promise, + request-with-body, + }; + + /// Configuration for `next-request`. + record next-request-options { + timeout-ms: option, + + /// Additional options may be added in the future via this resource type. + extra: option>, + } + + /// Extensibility for `next-request-options` + resource extra-next-request-options {} + + /// Starts waiting for the next request. + next-request: func( + options: next-request-options, + ) -> result; + + /// Waits until the next request is available, and then returns the resulting + /// request and body. + await-next-request: func( + pending: request-promise, + ) -> result; + + next-request-abandon: func( + pending: request-promise, + ) -> result<_, error>; + + /// Returns the client request's header names exactly as they were originally received. + /// + /// This includes both the original header name characters' cases, as well as the original order + /// of the received headers. + /// + /// The first `cursor` names are skipped. The remaining names are encoded successively with + /// a NUL byte after each into a list of bytes at most `max-len` long. If any of the remaining + /// names don't fit, the returned `option` is the index of the first name that didn't fit, + /// or `none` if all the remaining names fit. If `max-len` is too small to fit any name, + /// an `error.buffer-len` error is returned, providing a recommended buffer size. + downstream-original-header-names: func( + ds-request: borrow, + max-len: u64, + cursor: u32, + ) -> result>, error>; + + /// Returns the number of headers in the client request as originally received. + downstream-original-header-count: func( + ds-request: borrow + ) -> result; + + /// Returns the IP address of the client making the HTTP request, if known. + downstream-client-ip-addr: func( + ds-request: borrow + ) -> option; + + /// Returns the IP address on which this server received the HTTP request, if known. + downstream-server-ip-addr: func( + ds-request: borrow + ) -> option; + + /// Gets the HTTP/2 fingerprint of client request if available. + downstream-client-h2-fingerprint: func( + ds-request: borrow, + max-len: u64 + ) -> result; + + /// Gets the id of the current request if available. + downstream-client-request-id: func( + ds-request: borrow, + max-len: u64 + ) -> result; + + /// Gets the fingerprint of client request headers if available. + downstream-client-oh-fingerprint: func( + ds-request: borrow, + max-len: u64 + ) -> result; + + /// Returns whether the request was tagged as contributing to a DDoS attack. + downstream-client-ddos-detected: func( + ds-request: borrow + ) -> result; + + /// Gets the cipher suite used to secure the downstream client TLS connection. + /// + /// The value returned will be consistent with the [OpenSSL name] for the cipher suite. + /// + /// [OpenSSL name]: https://testssl.sh/openssl-iana.mapping.html + downstream-tls-cipher-openssl-name: func( + ds-request: borrow, + max-len: u64 + ) -> result, error>; + + /// Gets the TLS protocol version used to secure the downstream client TLS connection. + downstream-tls-protocol: func( + ds-request: borrow, + max-len: u64 + ) -> result, error>; + + /// Gets the raw bytes sent by the client in the TLS ClientHello message. + /// + /// See [RFC 5246] for details. + /// + /// [RFC 5246]: https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2 + downstream-tls-client-hello: func( + ds-request: borrow, + max-len: u64 + ) -> result, error>; + + /// Gets the raw client certificate used to secure the downstream client mTLS connection. + /// + /// The value returned will be based on PEM format. + /// + /// Returns `error.optional-none` if the connection is not mTLS or is unavailable. + downstream-tls-raw-client-certificate: func( + ds-request: borrow, + max-len: u64 + ) -> result, error>; + + /// Returns the `client-cert-verify-result` from the downstream client mTLS handshake. + /// + /// Returns `none` if not available. + downstream-tls-client-cert-verify-result: func( + ds-request: borrow + ) -> result; + + /// Gets the JA3 hash of the TLS ClientHello message. + downstream-tls-ja3-md5: func( + ds-request: borrow + ) -> result, error>; - endpoint-get: func(name: string) -> result; + /// Gets the JA4 hash of the TLS ClientHello message. + downstream-tls-ja4: func( + ds-request: borrow, + max-len: u64 + ) -> result; + + /// Gets the compliance region that the client IP address is in. + downstream-compliance-region: func( + ds-request: borrow, + max-len: u64 + ) -> result; - write: func(h: handle, msg: list) -> result; + /// Returns whether or not the original client request arrived with a + /// Fastly-Key belonging to a user with the rights to purge content on this + /// service. + fastly-key-is-valid: func( + ds-request: borrow, + ) -> result; } -/// Fastly HTTP Req +/// HTTP requests. interface http-req { - use types.{error}; - use http-types.{ - body-handle, request-handle, http-version, response, pending-request-handle, - content-encodings, framing-headers-mode, backend-config-options, - dynamic-backend-config - }; + use types.{error, ip-address}; + use http-types.{http-version, content-encodings, framing-headers-mode, tls-version}; + use http-resp.{response}; + use http-body.{body}; + use secret-store.{secret}; + use http-resp.{response-with-body}; + + /// Handle that can be used to wait for a sent request. + use async-io.{pollable as pending-request}; + + /// Handle that can be used to wait for incoming requests. + use async-io.{pollable as request-promise}; + + /// An HTTP request. + resource request { + /// Creates a new `request` with no method, URL, or headers, and an empty body. + new: static func() -> result; + + /// Sets the cache override behavior for this request. + /// + /// This setting will override any cache directive headers returned in response to this request. + set-cache-override: func( + cache-override: cache-override, + ) -> result<_, error>; + + /// Reads the request's header names via a buffer of the provided size. + /// + /// The first `cursor` names are skipped. The remaining names are encoded successively with + /// a NUL byte after each into a list of bytes at most `max-len` long. If any of the remaining + /// names don't fit, the returned `option` is the index of the first name that didn't fit, + /// or `none` if all the remaining names fit. If `max-len` is too small to fit any name, + /// an `error.buffer-len` error is returned, providing a recommended buffer size. + get-header-names: func( + max-len: u64, + cursor: u32, + ) -> result>, error>; + + /// Gets the value of a header, or `none` if the header is not present. + /// + /// If there are multiple values for the header, only one is returned. See + /// `get-header-values` if you need to get all of the values. + /// + /// If header name requires more than `max-len` bytes, this will return an `error.buffer-len` + /// containing the required size. + get-header-value: func( + name: string, + max-len: u64, + ) -> result>, error>; + + /// Gets multiple header values for the given `name` via a buffer of the provided size. + /// + /// As opposed to `get-header-value`, this function returns all of the values for this header. + /// + /// The first `cursor` values are skipped. The remaining values are encoded successively with + /// a NUL byte after each into a list of bytes at most `max-len` long. If any of the remaining + /// values don't fit, the returned `option` is the index of the first value that didn't + /// fit, or `none` if all the remaining values fit. If `max-len` is too small to fit any value, + /// an `error.buffer-len` error is returned, providing a recommended buffer size. + get-header-values: func( + name: string, + max-len: u64, + cursor: u32 + ) -> result, option>, error>; + + /// Sets the values for the given header name, replacing any headers that previously existed for + /// that name. + set-header-values: func( + name: string, + /// contains multiple values each terminated by `\0` and concatenated + values: list + ) -> result<_, error>; + + /// Sets a request header to the given value, discarding any previous values for the given + /// header name. + insert-header: func(name: string, value: list) -> result<_, error>; + + /// Adds a request header with given value. + /// + /// Unlike `set-header-values`, this does not discard existing values for the same header name. + append-header: func( + name: string, + value: list, + ) -> result<_, error>; + + /// Removes all request headers of the given name + /// + /// Returns `ok` if any headers were successfully removed. + remove-header: func(name: string) -> result<_, error>; + + /// Gets the request method. + get-method: func(max-len: u64) -> result; + + /// Sets the request method. + set-method: func(method: string) -> result<_, error>; + + /// Gets the request URI. + get-uri: func(max-len: u64) -> result; + + /// Sets the request URI. + set-uri: func(uri: string) -> result<_, error>; + + /// Gets the HTTP version of this request. + get-version: func() -> result; + + /// Sets the HTTP version of this request. + set-version: func(version: http-version) -> result<_, error>; + + /// Sets the content encodings to automatically decompress responses to this request. + /// + /// If the response to this request is encoded by one of the encodings set by this method, the + /// response will be presented to the Compute program in decompressed form with the + /// `Content-Encoding` and `Content-Length` headers removed. + set-auto-decompress-response: func( + encodings: content-encodings, + ) -> result<_, error>; + + /// Passes the WebSocket directly to a backend. + /// + /// This can only be used on services that have the WebSockets feature enabled and on requests + /// that are valid WebSocket requests. + /// + /// The sending completes in the background. Once this method has been called, no other response + /// can be sent to this request, and the application can exit without affecting the send. + /// + /// See the [WebSockets passthrough] documentation for a high-level description of this feature. + /// + /// [WebSockets passthrough]: https://www.fastly.com/documentation/guides/concepts/real-time-messaging/websockets-tunnel/ + redirect-to-websocket-proxy: func( + backend: string, + ) -> result<_, error>; + + /// Sets how the framing headers `Content-Length` and `Transfer-Encoding` will be determined + /// when sending this request. + set-framing-headers-mode: func( + mode: framing-headers-mode, + ) -> result<_, error>; + + /// Inspects request HTTP traffic using the [NGWAF] lookaside service. + /// + /// Returns a JSON-encoded string. + /// + /// [NGWAF]: https://docs.fastly.com/en/ngwaf/ + inspect: func( + body: borrow, + options: inspect-options, + max-len: u64 + ) -> result; + + /// Instead of having this request cache in this service's space, use the + /// cache of the named service + on-behalf-of: func( + service: string, + ) -> result<_, error>; + + redirect-to-grip-proxy: func( + backend: string, + ) -> result<_, error>; + } + + /// Retrieves a response for the request, either from cache or by sending it + /// to the given backend server. + /// + /// Returns once the response headers have been received, or an error occurs. + send: func( + request: request, + body: body, + backend: string, + ) -> result; + + /// Sends the request directly to the backend server without performing any + /// caching or inserting any cache-related headers in the response. + /// + /// Returns once the response headers have been received, or an error occurs. + send-uncached: func( + request: request, + body: body, + backend: string, + ) -> result; + + /// Begins sending the request to the given backend server, and returns a + /// `pending-request` that can yield the backend response or an error. + /// + /// This method returns as soon as the request begins sending to the backend, + /// and transmission of the request body and headers will continue in the + /// background. + /// + /// This method allows for sending more than one request at once and receiving + /// their responses in arbitrary orders. See `pending-request` for more + /// details on how to wait on, poll, or select between pending requests. + /// + /// This method is also useful for sending requests where the response is + /// unimportant, but the request may take longer than the Compute program is + /// able to run, as the request will continue sending even after the program + /// that initiated it exits. + send-async: func( + request: request, + body: body, + backend: string + ) -> result; + + /// This is to `send-async` as `send-uncached` is to `send`. + /// + /// As with `send-uncached`, this function sends the request directly to the + /// backend server without performing any caching or inserting any + /// cache-related headers in the response. + send-async-uncached: func( + request: request, + body: body, + backend: string, + ) -> result; + + /// Begins sending the request to the given backend server, and returns a + /// `pending-request` that can yield the backend response or an error. + /// + /// The `body` argument is not consumed, so that it can accept further data to send. + /// + /// The backend connection is only closed once `http-body.close` is called. The + /// `pending-request` will not yield a `response` until the body is finished. + /// + /// This method is most useful for programs that do some sort of processing or + /// inspection of a potentially-large client request body. Streaming allows the + /// program to operate on small parts of the body rather than having to read it all + /// into memory at once. + /// + /// This method returns as soon as the request begins sending to the backend, + /// and transmission of the request body and headers will continue in the + /// background. + send-async-streaming: func( + request: request, + body: borrow, + backend: string, + ) -> result; + + /// This is to `send-async-streaming` as `send-uncached` is to `send`. + /// + /// As with `send-uncached`, this function sends the request directly to the + /// backend server without performing any caching or inserting any + /// cache-related headers in the response. + send-async-uncached-streaming: func( + request: request, + body: borrow, + backend: string, + ) -> result; + + type request-with-body = tuple; + + /// Optional override for response caching behavior. + variant cache-override { + /// Do not override the behavior specified in the origin response’s cache control headers. + none, - /// An override for response caching behavior. - /// A zero value indicates that the origin response's cache control headers should be used. - flags cache-override-tag { - /// Do not cache the response to this request, regardless of the origin response's headers. + /// Do not cache the response to this request, regardless of the origin response’s headers. pass, - ttl, - stale-while-revalidate, - pci, + + /// Override particular cache control settings. + override(cache-override-details) + } + + /// The fields for the `override` arm of `cache-override`. + /// + /// The origin response’s cache control headers will be used for ttl and + /// `stale-while-revalidate` if `none`. + record cache-override-details { + ttl: option, + stale-while-revalidate: option, + pci: bool, + surrogate-key: option>, + + /// Additional options may be added in the future via this resource type. + extra: option>, } + /// Extensibility for `cache-override-details` + resource extra-cache-override-details {} + /// TLS client certificate verified result from downstream. enum client-cert-verify-result { /// Success value. @@ -269,7 +733,7 @@ interface http-req { /// bad certificate error. /// /// This error means the certificate is corrupt - /// (e.g., the certificate signatures do not verify correctly). + /// (for example, when the certificate signatures do not verify correctly). bad-certificate, /// certificate revoked error. /// @@ -306,13 +770,13 @@ interface http-req { /// hostname. dns-timeout, /// The system encountered a DNS error when trying to find an IP address for the backend - /// hostname. The fields $dns_error_rcode and $dns_error_info_code may be set in the + /// hostname. The fields `dns-error-rcode` and `dns-error-info-code` may be set in the /// $send_error_detail. dns-error, /// The system cannot determine which backend to use, or the specified backend was invalid. destination-not-found, - /// The system considers the backend to be unavailable; e.g., recent attempts to communicate - /// with it may have failed, or a health check may indicate that it is down. + /// The system considers the backend to be unavailable, for example when recent attempts to + /// communicate with it may have failed, or a health check may indicate that it is down. destination-unavailable, /// The system cannot find a route to the next-hop IP address. destination-ip-unroutable, @@ -345,8 +809,9 @@ interface http-req { /// The process of negotiating an upgrade of the HTTP version between the system and the /// backend failed. http-upgrade-failed, - /// The system encountered an HTTP protocol error when communicating with the backend. This - /// error will only be used when a more specific one is not defined. + /// The system encountered an HTTP protocol error when communicating with the backend. + /// + /// This error will only be used when a more specific one is not defined. http-protocol-error, /// An invalid cache key was provided for the request. http-request-cache-key-invalid, @@ -354,7 +819,7 @@ interface http-req { http-request-uri-invalid, /// The system encountered an unexpected internal error. internal-error, - /// The system received a TLS alert from the backend. The field $tls_alert_id may be set in + /// The system received a TLS alert from the backend. The field `tls-alert-id` may be set in /// the $send_error_detail. tls-alert-received, /// The system encountered a TLS error when communicating with the backend, either during @@ -362,19 +827,11 @@ interface http-req { tls-protocol-error, } - flags send-error-detail-mask { - reserved, - dns-error-rcode, - dns-error-info-code, - tls-alert-id, - } - record send-error-detail { tag: send-error-detail-tag, - mask: send-error-detail-mask, - dns-error-rcode: u16, - dns-error-info-code: u16, - tls-alert-id: u8, + dns-error-rcode: option, + dns-error-info-code: option, + tls-alert-id: option, } record error-with-detail { @@ -382,625 +839,714 @@ interface http-req { error: error, } - flags inspect-config-options { - reserved, - corp, - workspace, - } + /// Configuration for inspecting a `request` using Security. + record inspect-options { + corp: option, + workspace: option, - record inspect-config { - corp: string, - workspace: string, + /// Additional options may be added in the future via this resource type. + extra: option>, } - cache-override-set: func( - h: request-handle, - tag: cache-override-tag, - ttl: u32, - stale-while-revalidate: u32, - ) -> result<_, error>; - - cache-override-v2-set: func( - h: request-handle, - tag: cache-override-tag, - ttl: u32, - stale-while-revalidate: u32, - sk: option> - ) -> result<_, error>; - - downstream-client-ip-addr: func() -> result, error>; - - downstream-server-ip-addr: func() -> result, error>; + /// Extensibility for `inspect-options` + resource extra-inspect-options {} - downstream-client-h2-fingerprint: func(max-len: u64) -> result, error>; + /// Waits until the request is completed, and then returns the resulting + /// response and body. + await-request: func( + pending: pending-request + ) -> result; - downstream-client-request-id: func(max-len: u64) -> result, error>; + /// Closes the `request`, releasing any associated resources. + /// + /// A `request` is automatically consumed when you send a request. You should call `close` + /// only if you have a `request` you don't intend to use anymore. + close: func(request: request) -> result<_, error>; - downstream-client-oh-fingerprint: func(max-len: u64) -> result, error>; + upgrade-websocket: func(backend: string) -> result<_, error>; - downstream-client-ddos-detected: func() -> result; + /// Create a backend for later use + register-dynamic-backend: func( + prefix: string, + target: string, + options: dynamic-backend-options, + ) -> result<_, error>; - downstream-tls-cipher-openssl-name: func(max-len: u64) -> result, error>; + /// Create a backend for later use + resource dynamic-backend-options { + constructor(); + + host-override: func(value: string); + connect-timeout: func(value: u32); + first-byte-timeout: func(value: u32); + between-bytes-timeout: func(value: u32); + use-tls: func(value: bool); + tls-min-version: func(value: tls-version); + tls-max-version: func(value: tls-version); + cert-hostname: func(value: string); + ca-cert: func(value: string); + ciphers: func(value: string); + sni-hostname: func(value: string); + client-cert: func(client-cert: string, key: borrow); + http-keepalive-time-ms: func(value: u32); + tcp-keepalive-enable: func(value: u32); + tcp-keepalive-interval-secs: func(value: u32); + tcp-keepalive-probes: func(value: u32); + tcp-keepalive-time-secs: func(value: u32); + pooling: func(value: bool); + grpc: func(value: bool); + } +} - downstream-tls-protocol: func(max-len: u64) -> result, error>; +/// HTTP responses. +interface http-resp { + use types.{error, ip-address}; - downstream-tls-client-hello: func(max-len: u64) -> result, error>; + use http-types.{ + http-version, http-status, + framing-headers-mode + }; + use http-body.{body}; - downstream-tls-raw-client-certificate: func(max-len: u64) -> result, error>; + /// An HTTP response. + resource response { + /// Create a new `response`. + /// + /// The new `response` is created with status code 200 OK, no headers, and an empty body. + new: static func() -> result; - downstream-tls-client-cert-verify-result: func() -> result; + /// Read the response's header names via a buffer of the provided size. + /// + /// The first `cursor` names are skipped. The remaining names are encoded successively with + /// a NUL byte after each into a list of bytes at most `max-len` long. If any of the remaining + /// names don't fit, the returned `option` is the index of the first name that didn't fit, + /// or `none` if all the remaining names fit. If `max-len` is too small to fit any name, + /// an `error.buffer-len` error is returned, providing a recommended buffer size. + get-header-names: func( + max-len: u64, + cursor: u32, + ) -> result>, error>; + + /// Gets the value of a header, or `none` if the header is not present. + /// + /// If there are multiple values for the header, only one is returned. See + /// `get-header-values` if you need to get all of the values. + /// + /// If header name requires more than `max-len` bytes, this will return an `error.buffer-len` + /// containing the required size. + get-header-value: func( + name: string, + max-len: u64, + ) -> result>, error>; + + /// Gets multiple header values for the given `name` via a buffer of the provided size. + /// + /// As opposed to `get-header-value`, this function returns all of the values for this header. + /// + /// The first `cursor` values are skipped. The remaining values are encoded successively with + /// a NUL byte after each into a list of bytes at most `max-len` long. If any of the remaining + /// values don't fit, the returned `option` is the index of the first value that didn't + /// fit, or `none` if all the remaining values fit. If `max-len` is too small to fit any value, + /// an `error.buffer-len` error is returned, providing a recommended buffer size. + get-header-values: func( + name: string, + max-len: u64, + cursor: u32 + ) -> result, option>, error>; + + /// Sets the values for the given header name, replacing any headers that previously existed for + /// that name. + set-header-values: func( + name: string, + /// contains multiple values each terminated by `\0` and concatenated + values: list + ) -> result<_, error>; + + /// Sets a response header to the given value, discarding any previous values for the given + /// header name. + insert-header: func( + name: string, + value: list, + ) -> result<_, error>; + + /// Add a response header with given value. + /// + /// Unlike `set-header-values`, this does not discard existing values for the same header name. + append-header: func( + name: string, + value: list, + ) -> result<_, error>; - downstream-tls-ja3-md5: func() -> result, error>; + /// Remove all response headers of the given name + /// + /// Returns `ok` if any headers were successfully removed. + remove-header: func(name: string) -> result<_, error>; - downstream-tls-ja4: func(max-len: u64) -> result, error>; + /// Gets the HTTP version of this response. + get-version: func() -> result; - downstream-compliance-region: func(max-len: u64) -> result, error>; + /// Sets the HTTP version of this response. + set-version: func(version: http-version) -> result<_, error>; - new: func() -> result; + /// Gets the HTTP status code of the response. + get-status: func() -> result; - header-names-get: func( - h: request-handle, - max-len: u64, - cursor: u32, - ) -> result, option>>, error>; + /// Sets the HTTP status code of the response. + set-status: func(status: http-status) -> result<_, error>; - original-header-names-get: func( - max-len: u64, - cursor: u32, - ) -> result, option>>, error>; + /// Sets how the framing headers `Content-Length` and `Transfer-Encoding` will be determined + /// when sending this response. + set-framing-headers-mode: func(mode: framing-headers-mode) -> result<_, error>; - original-header-count: func() -> result; + /// Adjust the response's connection reuse mode. + set-http-keepalive-mode: func(mode: keepalive-mode) -> result<_, error>; - header-value-get: func( - h: request-handle, - name: list, - max-len: u64, - ) -> result>, error>; + /// Gets the destination IP address used for this response, if known. + get-remote-ip-addr: func() -> option; - header-values-get: func( - h: request-handle, - name: list, - max-len: u64, - cursor: u32 - ) -> result, option>>, error>; + /// Gets the destination port used for this response, if known. + get-remote-port: func() -> option; + } - header-values-set: func( - h: request-handle, - name: list, - /// contains multiple values separated by \0 - values: list + /// Sends a response to the client that made the request passed to `http-incoming.handle`. + /// + /// This method returns as soon as the response header begins sending to the client, and + /// transmission of the response will continue in the background. + /// + /// Data for the body must be written before calling this function. To start a response + /// and write data to it afterwards, use `send-downstream-streaming` instead. + send-downstream: func( + response: response, + body: body, ) -> result<_, error>; - header-insert: func(h: request-handle, name: list, value: list) -> result<_, error>; - - header-append: func( - h: request-handle, - name: list, - value: list, + /// Starts a response to the client that made the request passed to `http-incoming.handle`. + /// + /// The body is left open, allowing data to be written after calling this function. + send-downstream-streaming: func( + response: response, + body: borrow, ) -> result<_, error>; - header-remove: func(h: request-handle, name: list) -> result<_, error>; + /// Closes the `response`, releasing any associated resources. + /// + /// A `response` is consumed when you send a response to a client or stream one to a + /// client. You should call `close` only if you have a `response` you don't intend + /// to use anymore. + close: func(response: response) -> result<_, error>; - method-get: func(h: request-handle, max-len: u64) -> result; + type response-with-body = tuple; - method-set: func(h: request-handle, method: string) -> result<_, error>; + enum keepalive-mode { + automatic, + no-keepalive, + } +} - uri-get: func(h: request-handle, max-len: u64) -> result; +/// [Compute Dictionaries] (deprecated in favor of `config-store`) +/// +/// [Compute Dictionaries]: https://www.fastly.com/documentation/guides/concepts/edge-state/dynamic-config/#dictionaries +interface dictionary { - uri-set: func(h: request-handle, uri: string) -> result<_, error>; + use types.{error}; - version-get: func(h: request-handle) -> result; + /// A Compute Dictionary. + resource dictionary { + /// Opens a dictionary, given its name. + /// + /// Names are case sensitive. + open: static func(name: string) -> result; - version-set: func(h: request-handle, version: http-version) -> result<_, error>; + /// Tries to look up a value in this dictionary. + /// + /// If the lookup is successful, this function returns `ok(s)` containing the found string + /// `s`, or `err(error.optional-none)` if no entry with the given key was found. + get: func( + key: string, + max-len: u64, + ) -> result, error>; + } +} - send: func( - h: request-handle, - b: body-handle, - backend: string, - ) -> result; +/// [Geographic data] for IP addresses. +/// +/// [Geographic data]: https://www.fastly.com/blog/improve-performance-and-gain-better-end-user-intelligence-geoip-geography-detection +interface geo { + use types.{error, ip-address}; - send-v2: func( - h: request-handle, - b: body-handle, - backend: string, - ) -> result; + /// Looks up the geographic data associated with a particular IP address. + /// + /// Returns a list of bytes containing JSON-encoded geographic data. See [here] for descriptions + /// of the JSON fields. + /// + /// [here]: https://www.fastly.com/documentation/reference/vcl/variables/geolocation/ + lookup: func(ip-addr: ip-address, max-len: u64) -> result; +} - send-v3: func( - h: request-handle, - b: body-handle, - backend: string, - ) -> result; +/// Device detection based on the User-Agent header. +interface device-detection { + use types.{error}; - send-async: func(h: request-handle, b: body-handle, backend: string) -> -result; + /// Looks up the data associated with a particular User-Agent string. + /// + /// Returns a list of bytes containing JSON-encoded device data. See [here] for descriptions + /// of the JSON fields. + /// + /// [here]: https://www.fastly.com/documentation/reference/vcl/variables/client-request/client-identified/ + lookup: func(user-agent: string, max-len: u64) -> result, error>; +} - send-async-v2: func( - req-handle: request-handle, - body-handle: body-handle, - backend: string, - streaming: bool, - ) -> result; - - send-async-streaming: func( - h: request-handle, - b: body-handle, - backend: string - ) -> result; - - pending-req-poll: func( - h: pending-request-handle, - ) -> result, error>; - - pending-req-poll-v2: func( - h: pending-request-handle, - ) -> result, error-with-detail>; - - pending-req-wait: func(h: pending-request-handle) -> result; - - pending-req-wait-v2: func( - h: pending-request-handle - ) -> result; - - pending-req-select: func( - h: list - ) -> result, error>; - - pending-req-select-v2: func( - h: list - ) -> result>, error>; - - /// Returns whether or not the original client request arrived with a - /// Fastly-Key belonging to a user with the rights to purge content on this - /// service. - fastly-key-is-valid: func() -> result; - - close: func(h: request-handle) -> result<_, error>; - - auto-decompress-response-set: func( - h: request-handle, - encodings: content-encodings, - ) -> result<_, error>; - - upgrade-websocket: func(backend: string) -> result<_, error>; - - redirect-to-websocket-proxy: func(backend: string) -> result<_, error>; - - redirect-to-websocket-proxy-v2: func( - h: request-handle, - backend: string, - ) -> result<_, error>; - - redirect-to-grip-proxy: func(backend: string) -> result<_, error>; - - redirect-to-grip-proxy-v2: func( - h: request-handle, - backend: string, - ) -> result<_, error>; - - /// Adjust how this requests's framing headers are determined. - framing-headers-mode-set: func( - h: request-handle, - mode: framing-headers-mode, - ) -> result<_, error>; - - /// Create a backend for later use - register-dynamic-backend: func( - prefix: string, - target: string, - options: backend-config-options, - config: dynamic-backend-config, - ) -> result<_, error>; - - /// Hostcall for Fastly Compute guests to inspect request HTTP traffic - /// using the NGWAF lookaside service. - inspect: func( - h: request-handle, - b: body-handle, - options: inspect-config-options, - info: inspect-config, - max-len: u64 - ) -> result, error>; - - /// Instead of having this request cache in this service's space, use the - /// cache of the named service - on-behalf-of: func( - h: request-handle, - service: string, - ) -> result<_, error>; -} - -/// Fastly HTTP Resp -interface http-resp { - use types.{error}; - - use http-types.{ - response-handle, body-handle, http-version, http-status, - framing-headers-mode - }; - - new: func() -> result; - - header-names-get: func( - h: response-handle, - max-len: u64, - cursor: u32, - ) -> result, option>>, error>; - - header-value-get: func( - h: response-handle, - name: list, - max-len: u64, - ) -> result>, error>; - - header-values-get: func( - h: response-handle, - name: list, - max-len: u64, - cursor: u32 - ) -> result, option>>, error>; - - header-values-set: func( - h: response-handle, - name: list, - /// contains multiple values separated by \0 - values: list - ) -> result<_, error>; - - header-insert: func( - h: response-handle, - name: list, - value: list, - ) -> result<_, error>; - - header-append: func( - h: response-handle, - name: list, - value: list, - ) -> result<_, error>; - - header-remove: func( - h: response-handle, - name: list, - ) -> result<_, error>; - - version-get: func(h: response-handle) -> result; - - version-set: func( - h: response-handle, - version: http-version, - ) -> result<_, error>; - - send-downstream: func( - h: response-handle, - b: body-handle, - streaming: bool, - ) -> result<_, error>; - - status-get: func(h: response-handle) -> result; - - status-set: func(h: response-handle, status: http-status) -> result<_, error>; - - close: func(h: response-handle) -> result<_, error>; - - /// Adjust how this response's framing headers are determined. - framing-headers-mode-set: func(h: response-handle, mode: framing-headers-mode) --> result<_, error>; - - enum keepalive-mode { - automatic, - no-keepalive, - } - - /// Adjust the response's connection reuse mode. - http-keepalive-mode-set: func(h: response-handle, mode: keepalive-mode) -> -result<_, error>; - - /// Hostcall for getting the destination IP used for this request. - /// - /// The buffer for the IP address must be 16 bytes. `addr_octets_out` - /// will be set to 4 for IPv4 addresses, and 16 for IPv6. - get-addr-dest-ip: func(h: response-handle) -> result, error>; - - /// Hostcall for getting the destination port used for this request. - get-addr-dest-port: func(h: response-handle) -> result; -} - -/// Fastly Dictionary -interface dictionary { - - use types.{error}; - - /// A handle to an Edge Dictionary. - type handle = u32; - - open: func(name: string) -> result; - - get: func( - h: handle, - key: string, - max-len: u64, - ) -> result, error>; -} - -/// Fastly Geo -interface geo { - use types.{error}; - - lookup: func(addr-octets: list, max-len: u64) -> result, error>; -} - -/// Fastly device detection -interface device-detection { - use types.{error}; - - lookup: func(user-agent: string, max-len: u64) -> result>, error>; -} - -/// Fastly edge-rate-limiter +/// [Edge rate limiting] API. +/// +/// [Edge rate limiting]: https://docs.fastly.com/products/edge-rate-limiting interface erl { use types.{error}; + /// Increments an entry in a rate counter and check if the client has exceeded some average number + /// of requests per second (RPS) over the window. + /// + /// If the client is over the rps limit for the window, add to the penaltybox for ttl. Valid ttl + /// span is 1m to 1h and TTL value is truncated to the nearest minute. check-rate: func( - rc: string, + rate-counter: string, entry: string, delta: u32, window: u32, limit: u32, - pb: string, + penalty-box: string, ttl: u32, ) -> result; + /// Increments an entry in the ratecounter by `delta`. ratecounter-increment: func( - rc: string, + rate-counter: string, entry: string, delta: u32, ) -> result<_, error>; + /// Looks up the current rate for entry in the ratecounter for a window. ratecounter-lookup-rate: func( - rc: string, + rate-counter: string, entry: string, window: u32, ) -> result; + /// Looks up the current count for entry in the ratecounter for duration. ratecounter-lookup-count: func( - rc: string, + rate-counter: string, entry: string, duration: u32, ) -> result; + /// Add `entry` to a the penaltybox for the duration of ttl. + /// + /// Valid ttl span is 1m to 1h and TTL value is truncated to the nearest minute. penaltybox-add: func( - pb: string, + penalty-box: string, entry: string, ttl: u32, ) -> result<_, error>; + /// Checks if `entry` is in the penaltybox. penaltybox-has: func( - pb: string, + penalty-box: string, entry: string, - ) -> result; + ) -> result; } -/// Fastly Object Store +/// Object Store (deprecated in favor of `kv-store`) interface object-store { use types.{error}; - use http-types.{body-handle}; + use http-body.{body}; - /// (DEPRECATED) A handle to an Object Store. - type handle = u32; - /// (DEPRECATED) A handle to a pending Object Store lookup. - type pending-lookup-handle = u32; - /// (DEPRECATED) A handle to a pending Object Store insert. - type pending-insert-handle = u32; - /// (DEPRECATED) A handle to a pending Object Store delete. - type pending-delete-handle = u32; + /// (DEPRECATED) An Object Store. + resource store { + open: static func(name: string) -> result, error>; - open: func(name: string) -> result, error>; + lookup: func( + key: string, + ) -> result, error>; - lookup: func( - store: handle, - key: string, - ) -> result, error>; + lookup-async: func( + key: string, + ) -> result; - lookup-async: func( - store: handle, - key: string, - ) -> result; - - pending-lookup-wait: func( - handle: pending-lookup-handle, - ) -> result, error>; - - insert: func( - store: handle, - key: string, - body-handle: body-handle, - ) -> result<_, error>; + insert: func( + key: string, + body: body, + ) -> result<_, error>; - insert-async: func( - store: handle, - key: string, - body-handle: body-handle, - ) -> result; + insert-async: func( + key: string, + body: body, + ) -> result; - pending-insert-wait: func( - handle: pending-insert-handle, + delete-async: func( + key: string, + ) -> result; + } + /// (DEPRECATED) A pending Object Store lookup. + resource pending-lookup {} + /// (DEPRECATED) A pending Object Store insert. + resource pending-insert {} + /// (DEPRECATED) A pending Object Store delete. + resource pending-delete {} + + await-pending-lookup: func( + handle: pending-lookup, + ) -> result, error>; + + await-pending-insert: func( + handle: pending-insert, ) -> result<_, error>; - delete-async: func( - store: handle, - key: string, - ) -> result; - - pending-delete-wait: func( - handle: pending-delete-handle, + await-pending-delete: func( + handle: pending-delete, ) -> result<_, error>; } -/// Fastly KV Store (adapter) +/// Interface to Fastly's [Compute KV Store]. +/// +/// For a high-level introduction to this feature, see this [blog post]. /// -/// -- find the component version in github.com:fastly/edge-storage +/// [Compute KV Store]: https://www.fastly.com/documentation/guides/concepts/edge-state/data-stores/#kv-stores +/// [blog post]: https://www.fastly.com/blog/introducing-the-compute-edge-kv-store-global-persistent-storage-for-compute-functions interface kv-store { use types.{error}; - use http-types.{body-handle}; - - /// A handle to an KV Store. - type handle = u32; - /// A handle to a KV Store lookup. - type lookup-handle = u32; - /// A handle to a KV Store insert. - type insert-handle = u32; - /// A handle to a KV Store delete. - type delete-handle = u32; - /// A handle to a KV Store list. - type list-handle = u32; - - enum kv-status { - /// There was no error. - ok, - /// KV store cannot or will not process the request due to something that is perceived to be a client error - /// This will map to the api's 400 codes + use http-body.{body}; + + /// A KV Store. + resource store { + /// Opens the KV Store with the given name. + /// + /// If there is no store by that name, this returns `ok(none)`. + open: static func(name: string) -> result, error>; + + /// Looks up a value in the KV Store. + /// + /// Returns `ok(some(v))` with the value `v` that was found, `ok(none)` if no value was + /// found, or `err(e)` indicating the error `e` occurred. + /// + /// This function waits until the operation completes. + lookup: func( + key: string, + ) -> result, kv-error>; + + /// Look up a value in the KV Store asynchronously. + /// + /// This function initiates an async lookup of a value in the KV Store. Use + /// `await-lookup` to finish the lookup. + lookup-async: func( + key: string, + ) -> result; + + /// Inserts a value into the KV Store. + /// + /// If the KV Store already contains a value for this key, the `mode` field + /// of the `options` argument specifies how the existing value is handled. + /// + /// This function waits until the operation completes. + insert: func( + key: string, + body: body, + options: insert-options, + ) -> result<_, kv-error>; + + /// Insert a value into the KV Store asynchronously. + /// + /// If the KV Store already contains a value for this key, the `mode` field + /// of the `options` argument specifies how the existing value is handled. + /// + /// This function initiates an async insert of a value in the KV Store. Use + /// `await-insert` to finish the lookup. + insert-async: func( + key: string, + body: body, + options: insert-options, + ) -> result; + + /// Deletes a value in the KV Store. + /// + /// Returns `ok(true)` if a value was successfully deleted, `ok(false)` if no value was + /// found, or `err(e)` indicating the error `e` occurred. + /// + /// This function waits until the operation completes. + delete: func( + key: string, + ) -> result; + + /// Delete of a value in the KV Store. + /// + /// This function initiates an async delete of a value in the KV Store. Use + /// `await-delete` to finish the lookup. + delete-async: func( + key: string, + ) -> result; + + /// Lists keys in the KV Store. + /// + /// Returns `ok(b)` with the body `b` on success, or `err(e)` indicating the error `e` + /// occurred. + /// + /// This function waits until the operation completes. + %list: func( + options: list-options, + ) -> result; + + /// List of keys in the KV Store. + /// + /// This function initiates an async list value in the KV Store. Use + /// `await-list` to finish the lookup. + list-async: func( + options: list-options, + ) -> result; + } + + /// An asynchronous KV Store lookup. Use `await-lookup` to resolve. + use async-io.{pollable as pending-lookup}; + + /// An asynchronous KV Store insert. Use `await-insert` to resolve. + use async-io.{pollable as pending-insert}; + + /// An asynchronous KV Store delete. Use `await-delete` to resolve. + use async-io.{pollable as pending-delete}; + + /// An asynchronous KV Store list. Use `await-list` to resolve. + use async-io.{pollable as pending-list}; + + /// A value indicating the status of a KV store operation. + enum kv-error { + /// KV store cannot or will not process the request due to something that is perceived to be a + /// client error. + /// + /// This will map to the api's 400 codes. bad-request, - /// KV store cannot find the requested resource - /// This will map to the api's 404 codes - not-found, - /// KV store cannot fulfill the request, as definied by the client's prerequisites (ie. if-generation-match) - /// This will map to the api's 412 codes + /// KV store cannot fulfill the request, as defined by the client's prerequisites, for example + /// `if-generation-match`. + /// + /// This will map to the api's 412 codes. precondition-failed, /// The size limit for a KV store key was exceeded. - /// This will map to the api's 413 codes + /// + /// This will map to the api's 413 codes. payload-too-large, /// The system encountered an unexpected internal error. - /// This will map to all remaining http error codes + /// + /// This will map to all remaining http error codes. internal-error, /// Too many requests have been made to the KV store. - /// This will map to the api's 429 codes + /// + /// This will map to the api's 429 codes. too-many-requests, + /// Generic error value. + /// + /// This means that some unexpected error occurred. + generic-error, } - open: func(name: string) -> result, error>; + /// Wait on the async lookup of a value in the KV Store. + /// + /// Returns `ok(some(v))` with the value `v` that was found, `ok(none)` if no value was + /// found, or `err(e)` indicating the error `e` occurred. + await-lookup: func( + handle: pending-lookup, + ) -> result, kv-error>; - lookup: func( - store: handle, - key: list, - ) -> result; + /// Wait on the async insert of a value in the KV Store. + /// + /// Returns `ok` if the `insert` succeeded, or an error code on failure. + await-insert: func( + handle: pending-insert, + ) -> result<_, kv-error>; + + /// Wait on the async delete of a value in the KV Store. + /// + /// Returns `ok(true)` if a value was successfully deleted, `ok(false)` if no value was + /// found, or `err(e)` indicating the error `e` occurred. + await-delete: func( + handle: pending-delete, + ) -> result; + + /// Wait on the async list of keys in the KV Store. + /// + /// Returns `ok(b)` with the body `b` on success, or `err(e)` indicating the error `e` + /// occurred. + await-list: func( + handle: pending-list, + ) -> result; + + /// A response from a KV Store Lookup operation. + /// + /// This type holds the `body`, metadata, and generation of found key. + resource entry { + /// Take and return the body from this `entry`, if it has one; otherwise return `none`. + /// + /// After calling this method, this entry will no longer have a body. + take-body: func() -> option; - resource lookup-result { - body: func() -> body-handle; + /// Read the metadata of the KV Store item. metadata: func(max-len: u64) -> result, error>; + + /// Read the current generation of the KV Store item. generation: func() -> u64; } - lookup-wait: func( - handle: lookup-handle, - ) -> result, kv-status>, error>; - + /// Selects the behavior for an insert when the new key matches an existing key. + /// + /// A KV store maintains the property that its keys are unique from each other. If an insert + /// has a key that doesn't match any key already in the store, then the pair of the key and the + /// new value is inserted into the store. However, if the insert's key does match a key already + /// in the store, then no new key-value pair is inserted, and the insert's `insert-mode.mode` + /// determines what it does instead. enum insert-mode { + /// Updates the existing key's value by overwriting it with the new value. + /// + /// This is the default mode. overwrite, + + /// Fails, leaving the existing key's value unmodified. + /// + /// With this mode, the insert fails with a code of `kv-error.precondition-failed`, and + /// does not modify the existing value. Inserts with this mode will only “add” new key-value + /// pairs; they are prevented from modifying any existing ones. add, + + /// Updates the existing key's value by appending the new value to it. append, + + /// Updates the existing key's value by prepending the new value to it. prepend, } - flags insert-config-options { - reserved, - background-fetch, - if-generation-match, - metadata, - time-to-live-sec, - } + /// Options for configuring the behavior of the `insert` function. + record insert-options { + /// If set, allows fetching from the origin to occur in the background, enabling a faster + /// response with stale content. The cache will be updated with fresh content after the request + /// is completed. + background-fetch: bool, - record insert-config { - mode: insert-mode, - if-generation-match: u64, - metadata: string, - time-to-live-sec: u32, - } + /// Requests for keys will return a “generation” header specific to the version of a key. The + /// generation header is a unique, non-serial 64-bit unsigned integer that can be used for + /// testing against a specific KV store value. + if-generation-match: option, - insert: func( - store: handle, - key: list, - body-handle: body-handle, - mask: insert-config-options, - config: insert-config, - ) -> result; + /// Sets an arbitrary data field which can contain up to 2000B of data. + metadata: option, - insert-wait: func( - handle: insert-handle, - ) -> result; + /// Sets a time for the key to expire. Deletion will take place up to 24 hours after the ttl + /// reaches 0. + time-to-live-sec: option, - delete: func( - store: handle, - key: list, - ) -> result; + /// Select the behavior in the case when the new key matches an existing key. + mode: insert-mode, - delete-wait: func( - handle: delete-handle, - ) -> result; + /// Additional options may be added in the future via this resource type. + extra: option>, + } + /// Extensibility for `insert-options` + resource extra-insert-options {} + + /// Modes of KV Store list operations. + /// + /// This type serves to facilitate alternative methods of cache interactions with list operations. enum list-mode { + /// Performs an un-cached list on every invocation. + /// + /// This is the default method of listing. strong, - eventual, - } - flags list-config-options { - reserved, - cursor, - limit, - prefix, + /// Returns a cached list response to improve performance. + /// + /// The data may be slightly out of sync with the store, but repeated calls are faster. + /// + /// The word “eventual” here refers to eventual consistency. + eventual, } - record list-config { + record list-options { mode: list-mode, - cursor: string, - limit: u32, - prefix: string, - } + cursor: option, + limit: option, + prefix: option, - %list: func( - store: handle, - mask: list-config-options, - options: list-config, - ) -> result; + /// Additional options may be added in the future via this resource type. + extra: option>, + } - list-wait: func( - handle: list-handle, - ) -> result, kv-status>, error>; + /// Extensibility for `list-options` + resource extra-list-options {} } -/// Fastly Secret Store +/// [Secret Store] API. +/// +/// [Secret Store]: https://www.fastly.com/documentation/reference/api/services/resources/secret-store/ interface secret-store { - use types.{error, secret-handle}; - - /// A handle to a Secret Store. - type store-handle = u32; + use types.{error}; - open: func(name: string) -> result; + /// An individual secret. + resource secret { + /// Creates a new “secret” from the given memory. + /// + /// This is *not* the suggested way to create `secret`s; instead, we suggest using `get`. + /// This secret will *NOT* be shared with other sessions. + /// + /// This method can be used for data that should be secret, but is being obtained by + /// some other means than the secret store. New “secrets” created this way use plaintext + /// only, and live in the session's memory unencrypted for much longer than secrets + /// generated by `get`. They should thus only be used in situations in which an API requires + /// a `secret`, but you cannot (for whatever reason) use a `store` to store them. + /// + /// As the early note says, this `secret` will be local to the current session, and + /// will not be shared with other sessions of this service. + from-bytes: static func(bytes: list) -> result; + + /// Returns the plaintext value of this secret. + plaintext: func( + max-len: u64 + ) -> result>, error>; + } - get: func( - store: store-handle, - key: string, - ) -> result, error>; + /// A Secret Store. + resource store { + /// Opens the Secret Store with the given name. + open: static func(name: string) -> result; - plaintext: func( - secret: secret-handle, - max-len: u64 - ) -> result>, error>; - - from-bytes: func(bytes: list) -> result; + /// Tries to look up a Secret by name in this secret store. + /// + /// If successful, this method returns `ok(some(s))` containing the found secret `s` if the + /// secret is found, or `ok(none)` if the secret was not found. + get: func( + key: string, + ) -> result, error>; + } } -/// Fastly ACL +/// Blocklists using [Access Control Lists] (ACLs) +/// +/// [Access Control Lists]: https://www.fastly.com/documentation/reference/api/acls/ interface acl { - use types.{error}; - use http-types.{body-handle}; + use types.{error, ip-address}; + use http-body.{body}; - /// A handle to an ACL. - type acl-handle = u32; + /// An ACL. + resource acl { + /// Opens an ACL linked to the current service with the given link name. + open: static func(name: string) -> result; + /// Performs a lookup of the given IP address in the ACL. + /// + /// If no matches are found, then `ok(none)` is returned. + lookup: func( + ip-addr: ip-address, + ) -> result, acl-error>, error>; + } + + /// Errors returned on ACL lookup failure. enum acl-error { /// The $acl_error has not been initialized. uninitialized, @@ -1013,17 +1559,27 @@ interface acl { /// Too many requests have been made. too-many-requests, } - - open: func(name: string) -> result; - - lookup: func( - acl: acl-handle, - ip-octets: list, - ip-len: u64, - ) -> result, acl-error>, error>; } -/// Fastly backend +/// [Backends] API. +/// +/// A backend represents a service that the application can send requests to, potentially +/// caching the responses received. +/// +/// Backends come in one of two flavors: +/// * **Static Backends**: These backends are created using the Fastly UI or API, +/// and are predefined by the user. Static backends typically have short names that are +/// usable across every session of a service. +/// * **Dynamic Backends**: These backends are created programmatically using the +/// `register-dynamic-backend` API. They are defined at runtime, and may or may not +/// be shared across sessions depending on how they are configured. +/// +/// To use a backend, pass it to a `send*` function. +/// +/// Future versions of this function may return an error if your service does not have a backend +/// with this name. +/// +/// [Backends]: https://www.fastly.com/documentation/guides/integrations/non-fastly-services/developer-guide-backends/ interface backend { use types.{error}; use http-types.{tls-version}; @@ -1032,6 +1588,7 @@ interface backend { type timeout-secs = u32; type probe-count = u32; + /// Returns `true` if a backend with this name exists. exists: func(backend: string) -> result; enum backend-health { @@ -1040,217 +1597,468 @@ interface backend { unhealthy, } + /// Return the health of the backend if configured and currently known. + /// + /// For backends without a configured healthcheck, this will always return + /// `backend-health.unknown`. is-healthy: func(backend: string) -> result; - /// Returns `true` if the backend is a "dynamic" backend. + /// Returns `true` if the backend is a “dynamic” backend. is-dynamic: func(backend: string) -> result; - /// Get the host of this backend. + /// Gets the host of this backend. get-host: func(backend: string, max-len: u64) -> result; - /// Get the "override host" for this backend. + /// Gets the “override host” for this backend. + /// + /// This is used to change the `Host` header sent to the backend. See + /// [the Fastly documentation on override hosts]. /// - /// This is used to change the `Host` header sent to the backend. See the - /// Fastly documentation oh this topic here: https://docs.fastly.com/en/guides/specifying-an-override-host + /// [the Fastly documentation on override hosts]: https://docs.fastly.com/en/guides/specifying-an-override-host> get-override-host: func( backend: string, max-len: u64, ) -> result>, error>; - /// Get the remote TCP port of the backend connection for the request. + /// Gets the remote TCP port of the backend connection for the request. get-port: func(backend: string) -> result; - /// Get the connection timeout of the backend. - get-connect-timeout-ms: func(backend: string) -> result; + /// Gets the connection timeout of the backend. + get-connect-timeout-ms: func(backend: string) -> result; - /// Get the first byte timeout of the backend. - get-first-byte-timeout-ms: func(backend: string) -> result; + /// Gets the first byte timeout of the backend. + /// + /// This timeout applies between the time of connection and the time we get the first byte back. + get-first-byte-timeout-ms: func(backend: string) -> result; - /// Get the between byte timeout of the backend. - get-between-bytes-timeout-ms: func(backend: string) -> result; + /// Gets the between byte timeout of the backend. + /// + /// This timeout applies between any two bytes we receive across the wire. + get-between-bytes-timeout-ms: func(backend: string) -> result; /// Returns `true` if the backend is configured to use TLS. is-tls: func(backend: string) -> result; - /// Get the minimum TLS version this backend will use. + /// Gets the minimum TLS version this backend will use. get-tls-min-version: func(backend: string) -> result, error>; - /// Get the maximum SSL version this backend will use. + /// Gets the maximum TLS version this backend will use. get-tls-max-version: func(backend: string) -> result, error>; + /// Returns the time for this backend to hold onto an idle HTTP keepalive connection + /// after it was last used before closing it. get-http-keepalive-time: func( backend: string, ) -> result; + /// Returns `true` if TCP keepalives have been enabled for this backend. get-tcp-keepalive-enable: func( backend: string, ) -> result; + /// Returns the time to wait in between sending each TCP keepalive probe to this backend. get-tcp-keepalive-interval: func( backend: string, ) -> result; + /// Returns the time to wait after the last data was sent before starting to send TCP keepalive + /// probes to this backend. get-tcp-keepalive-probes: func( backend: string, ) -> result; + /// Returns the time to wait after the last data was sent before starting to send TCP keepalive + /// probes to this backend. get-tcp-keepalive-time: func( backend: string, ) -> result; } -/// Fastly Async IO +/// Async IO support. +/// +/// This module provides several utilities for performing I/O asynchronously. +/// See the documentation for `async-io.pollable` for a description of the kinds +/// of events it supports. +/// +/// In the future, this interface is expected to be replaced by +/// [integrated async features]. +/// +/// [integrated async features]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md#-async-explainer interface async-io { - use types.{error}; - - /// A handle to an object supporting generic async operations. - /// Can be either a `BodyHandle` or a `PendingRequestHandle`. + /// An object supporting generic async operations. + /// + /// Can be a `http-body.body`, `http-req.pending-request`, `http-req.request-promise`, + /// `cache.pending-entry`. `kv-store.pending-lookup`, `kv-store.pending-insert`, + /// `kv-store.pending-delete`, or `kv-store.pending-list`. /// /// Each async item has an associated I/O action: /// - /// * Pending requests: awaiting the response headers / `Response` object + /// * Pending requests: awaiting the response headers / `response` object /// * Normal bodies: reading bytes from the body /// * Streaming bodies: writing bytes to the body /// - /// For writing bytes, note that there is a large host-side buffer that bytes can eagerly be written - /// into, even before the origin itself consumes that data. - type handle = u32; + /// For writing bytes, there is a large buffer associated with the handle that bytes + /// can eagerly be written into, even before the origin itself consumes that data. + resource pollable { + /// Make a nonblocking attempt to complete the I/O operation. + /// + /// Returns `true` if the given async item is “ready” for its associated I/O action, `false` + /// otherwise. + /// + /// If an object is ready, the I/O action is guaranteed to complete without blocking. + /// + /// Valid object handles includes bodies and pending requests. See the `async-io.pollable` + /// definition for more details, including what I/O actions are associated with each handle + /// type. + is-ready: func() -> bool; - /// Blocks until one of the given objects is ready for I/O, or the optional timeout expires. + /// Create a new trivial `pollable` which reports being immediately ready. + new-ready: static func() -> pollable; + } + + /// Blocks until one of the given objects is ready for I/O. /// - /// Valid object handles includes bodies and pending requests. See the `async_item_handle` + /// If an object is ready, the I/O action is guaranteed to complete without blocking. + /// + /// Valid object handles includes bodies and pending requests. See the `async-io.pollable` /// definition for more details, including what I/O actions are associated with each handle /// type. /// - /// The timeout is specified in milliseconds, or 0 if no timeout is desired. + /// Returns the *index* (not handle!) of the first object that is ready. /// - /// Returns the _index_ (not handle!) of the first object that is ready, or - /// none if the timeout expires before any objects are ready for I/O. - select: func(hs: list, timeout-ms: u32) -> result, error>; + /// Traps if the list is empty. + select: func(handles: list>) -> u32; - /// Returns 1 if the given async item is "ready" for its associated I/O action, 0 otherwise. + /// Blocks until one of the given objects is ready for I/O, or the timeout expires. /// /// If an object is ready, the I/O action is guaranteed to complete without blocking. /// - /// Valid object handles includes bodies and pending requests. See the `async_item_handle` + /// Valid object handles includes bodies and pending requests. See the `async-io.pollable` /// definition for more details, including what I/O actions are associated with each handle /// type. - is-ready: func(handle: handle) -> result; + /// + /// The timeout is specified in milliseconds. + /// + /// Returns the *index* (not handle!) of the first object that is ready, or `none` if the + /// timeout expires before any objects are ready for I/O. + select-with-timeout: func(handles: list>, timeout-ms: u32) -> option; } -/// Fastly Purge -/// -/// See the [Fastly purge documentation] for details. +/// [Cache Purging] API. /// -/// [Fastly purge documentation]: https://www.fastly.com/documentation/guides/concepts/edge-state/cache/purging/ +/// [Cache Purging]: https://www.fastly.com/documentation/guides/concepts/edge-state/cache/purging/ interface purge { - use types.{error}; + use types.{error}; + + record purge-options { + /// Perform a [soft purge] instead of a hard purge. + /// + /// [soft purge]: https://www.fastly.com/documentation/guides/concepts/edge-state/cache/purging/#soft-vs-hard-purging + soft-purge: bool, + + /// Additional options may be added in the future via this resource type. + extra: option>, + } + + /// Extensibility for `purge-options` + resource extra-purge-options {} + + /// Purge a surrogate key for the current service. + /// + /// A surrogate key can be a max of 1024 characters. + /// A surrogate key must contain only printable ASCII characters (those between `0x21` and `0x7E`, + /// inclusive). + /// + /// Never returns `error.optional-none`. + purge-surrogate-key: func( + surrogate-keys: string, + purge-options: purge-options, + ) -> result<_, error>; + + /// Purge a surrogate key for the current service, and return the purge id. + /// + /// This is similar to `purge-surrogate-key`, but on success, returns a + /// [JSON purge response] containing an ASCII alphanumeric string identifying + /// a purging. + /// + /// Never returns `error.optional-none`. + /// + /// [JSON purge response]: https://developer.fastly.com/reference/api/purging/#purge-tag + purge-surrogate-key-verbose: func( + surrogate-keys: string, + purge-options: purge-options, + max-len: u64, + ) -> result; +} + +/// [Core Cache] API +/// +/// [Core Cache]: https://www.fastly.com/documentation/guides/concepts/edge-state/cache/#core-cache +interface cache { + + use types.{error}; + use http-body.{body}; + use http-req.{request}; + + /// The outcome of a cache lookup (either bare or as part of a cache transaction) + resource entry { + /// Performs a non-request-collapsing cache lookup. + /// + /// Returns a result without waiting for any request collapsing that may be ongoing. + lookup: static func( + key: list, + options: lookup-options, + ) -> result; + + /// The entrypoint to the request-collapsing cache transaction API. + /// + /// This operation always participates in request collapsing and may return stale objects. To + /// bypass request collapsing, use `lookup` and `insert` instead. + transaction-lookup: static func( + key: list, + options: lookup-options, + ) -> result; + + /// The entrypoint to the request-collapsing cache transaction API, returning instead of waiting + /// on busy. + /// + /// This operation always participates in request collapsing and may return stale objects. To + /// bypass request collapsing, use `lookup` and `insert` instead. + transaction-lookup-async: static func( + key: list, + options: lookup-options, + ) -> result; + + /// Insert an object into the cache with the given metadata. + /// + /// Can only be used in if the cache handle state includes the `must-insert-or-update` flag. + /// + /// The returned handle is to a streaming body that is used for writing the object into + /// the cache. + transaction-insert: func( + options: write-options, + ) -> result; + + /// Insert an object into the cache with the given metadata, and return a readable stream of the + /// bytes as they are stored. + /// + /// This helps avoid the “slow reader” problem on a teed stream, for example when a program + /// wishes to store a backend request in the cache while simultaneously streaming to a client + /// in an HTTP response. + /// + /// The returned body handle is to a streaming body that is used for writing the object *into* + /// the cache. The returned cache handle provides a separate transaction for reading out the + /// newly cached object to send elsewhere. + transaction-insert-and-stream-back: func( + options: write-options, + ) -> result, error>; + + /// Update the metadata of an object in the cache without changing its data. + /// + /// Can only be used in if the cache handle state includes both of the flags: + /// - `found` + /// - `must-insert-or-update` + transaction-update: func( + options: write-options, + ) -> result<_, error>; + + get-state: func() -> result; + + /// Gets the user metadata of the found object, returning `none` if no object + /// was found. + get-user-metadata: func(max-len: u64) -> result>, error>; + + /// Gets a range of the found object body, returning the `optional-none` error if there + /// was no found object. + /// + /// The returned `body` must be closed before calling this function again on the same + /// `entry`. + /// + /// Note: until the CacheD protocol is adjusted to fully support this functionality, + /// the body of objects that are past the stale-while-revalidate period will not + /// be available, even when other metadata is. + get-body: func( + options: get-body-options, + ) -> result; + + /// Gets the content length of the found object, returning the `error.optional-none` error if + /// there was no found object, or no content length was provided. + get-length: func() -> result; + + /// Gets the configured max age of the found object, returning the `error.optional-none` error + /// if there was no found object. + get-max-age-ns: func() -> result; + + /// Gets the configured stale-while-revalidate period of the found object, returning the + /// `error.optional-none` error if there was no found object. + get-stale-while-revalidate-ns: func() -> result; + + /// Gets the age of the found object, returning the `error.optional-none` error if there + /// was no found object. + get-age-ns: func() -> result; + + /// Gets the number of cache hits for the found object, returning the `error.optional-none` + /// error if there was no found object. + get-hits: func() -> result; + + /// Cancel an obligation to provide an object to the cache. + /// + /// Useful if there is an error before streaming is possible, for example if a backend is + /// unreachable. + transaction-cancel: func() -> result<_, error>; + } + /// Handle that can be used to check whether or not a cache lookup is waiting on another client. + use async-io.{pollable as pending-entry}; + + /// A replace operation. + type replace-entry = entry; + + /// The entrypoint to the replace API. + /// + /// This operation always participates in request collapsing and may return stale objects. + replace: func( + key: list, + options: replace-options, + ) -> result; + + /// Replace an object in the cache with the given metadata + /// + /// The returned handle is to a streaming body that is used for writing the object into + /// the cache. + replace-insert: func( + handle: borrow, + options: write-options, + ) -> result; + + /// Gets the age of the existing object during replace, returning + /// `none` if there was no object. + replace-get-age-ns: func( + handle: borrow, + ) -> result, error>; + + /// Gets a range of the existing object body, returning `none` if there + /// was no existing object. + /// + /// The returned `body` must be closed before calling this function + /// again on the same `replace-entry`. + replace-get-body: func( + handle: borrow, + options: get-body-options, + ) -> result, error>; + + /// Gets the number of cache hits for the existing object during replace, + /// returning `none` if there was no object. + replace-get-hits: func( + handle: borrow, + ) -> result, error>; - flags purge-options-mask { - soft-purge, - /// all ret_buf fields must be populated - ret-buf - } + /// Gets the content length of the existing object during replace, + /// returning `none` if there was no object, or no content + /// length was provided. + replace-get-length: func( + handle: borrow, + ) -> result, error>; - /// Purge a surrogate key for the current service. - /// - /// A surrogate key can be a max of 1024 characters. - /// A surrogate key must contain only printable ASCII characters (those between `0x21` and `0x7E`, inclusive). - /// - /// Returns a JSON purge response as in https://developer.fastly.com/reference/api/purging/#purge-tag - purge-surrogate-key: func( - surrogate-keys: string, - purge-options: purge-options-mask, - max-len: u64, - ) -> result, error>; -} + /// Gets the configured max age of the existing object during replace, + /// returning the `error.optional-none` error if there was no object. + replace-get-max-age-ns: func( + handle: borrow, + ) -> result, error>; -/// Fastly Cache -interface cache { + /// Gets the configured stale-while-revalidate period of the existing + /// object during replace, returning the `error.optional-none` error if there was no + /// object. + replace-get-stale-while-revalidate-ns: func( + handle: borrow, + ) -> result, error>; - use types.{error}; - use http-types.{body-handle, request-handle}; + /// Gets the lookup state of the existing object during replace, returning + /// the `error.optional-none` error if there was no object. + replace-get-state: func( + handle: borrow, + ) -> result, error>; + + /// Gets the user metadata of the existing object during replace, returning + /// the `error.optional-none` error if there was no object. + replace-get-user-metadata: func( + handle: borrow, + max-len: u64, + ) -> result>, error>; - /// The outcome of a cache lookup (either bare or as part of a cache transaction) - type handle = u32; - /// Handle that can be used to check whether or not a cache lookup is waiting on another client. - type busy-handle = u32; - type cache-replace-handle = u32; type object-length = u64; type duration-ns = u64; type cache-hit-count = u64; - flags lookup-options-mask { - reserved, - request-headers, - service-id, - always-use-requested-range, - } - - /// Extensible options for cache lookup operations; currently used for both `lookup` and `transaction_lookup`. + /// Options for cache lookup operations; currently used for both `lookup` and + /// `transaction-lookup`. record lookup-options { /// A full request handle, but used only for its headers - request-headers: request-handle, + /// + /// May be `none` if the `request-headers` option isn't enabled. + /// + request-headers: option>, - service-id: string, - } + service-id: option, - /// Options mask for `http_cache_write_options`. - flags write-options-mask { - reserved, - /// Only allowed for non-transactional `insert` - request-headers, - vary-rule, - initial-age-ns, - stale-while-revalidate-ns, - surrogate-keys, - length, - user-metadata, - sensitive-data, - edge-max-age-ns, - service-id, + always-use-requested-range: bool, + + /// Additional options may be added in the future via this resource type. + extra: option>, } - /// Configuration for several hostcalls that write to the cache: + /// Extensibility for `lookup-options` + resource extra-lookup-options {} + + /// Configuration for several functions that write to the cache: /// - `insert` /// - `transaction-insert` /// - `transaction-insert-and-stream-back` /// - `transaction-update` /// - /// Some options are only allowed for certain of these hostcalls; see `cache-write-options-mask`. + /// Some options are only allowed for certain of these hostcalls; see the comments + /// on the fields. record write-options { - /// this is a required field; there's no flag for it + /// this is a required field max-age-ns: duration-ns, /// a full request handle, but used only for its headers - request-headers: request-handle, + /// + /// Only allowed for non-transactional `insert` + request-headers: option>, /// a list of header names separated by spaces - vary-rule: string, + vary-rule: option, /// The initial age of the object in nanoseconds (default: 0). /// /// This age is used to determine the freshness lifetime of the object as well as to /// prioritize which variant to return if a subsequent lookup matches more than one vary rule - initial-age-ns: duration-ns, - stale-while-revalidate-ns: duration-ns, + initial-age-ns: option, + stale-while-revalidate-ns: option, /// a list of surrogate keys separated by spaces - surrogate-keys: string, - length: object-length, - user-metadata: list, - edge-max-age-ns: duration-ns, - service-id: string, + surrogate-keys: option, + length: option, + user-metadata: option>, + edge-max-age-ns: option, + service-id: option, + sensitive-data: bool, + + /// Additional options may be added in the future via this resource type. + extra: option>, } - flags get-body-options-mask { - reserved, - %from, - to, - } + /// Extensibility for `write-options` + resource extra-write-options {} record get-body-options { - %from: u64, - to: u64, + %from: option, + to: option, + + /// Additional options may be added in the future via this resource type. + extra: option>, } + /// Extensibility for `get-body-options` + resource extra-get-body-options {} + /// The status of this lookup (and potential transaction) flags lookup-state { /// a cached object was found @@ -1263,327 +2071,335 @@ interface cache { must-insert-or-update, } - /// Performs a non-request-collapsing cache lookup. - /// - /// Returns a result without waiting for any request collapsing that may be ongoing. - lookup: func( - key: list, - mask: lookup-options-mask, - options: lookup-options, - ) -> result; - /// Performs a non-request-collapsing cache insertion (or update). /// /// The returned handle is to a streaming body that is used for writing the object into /// the cache. insert: func( key: list, - options-mask: write-options-mask, options: write-options, - ) -> result; - - /// The entrypoint to the request-collapsing cache transaction API. - /// - /// This operation always participates in request collapsing and may return stale objects. To bypass - /// request collapsing, use `lookup` and `insert` instead. - transaction-lookup: func( - key: list, - mask: lookup-options-mask, - options: lookup-options, - ) -> result; - - /// The entrypoint to the request-collapsing cache transaction API, returning instead of waiting on busy. - /// - /// This operation always participates in request collapsing and may return stale objects. To bypass - /// request collapsing, use `lookup` and `insert` instead. - transaction-lookup-async: func( - key: list, - mask: lookup-options-mask, - options: lookup-options, - ) -> result; + ) -> result; /// Continues the lookup transaction from which the given busy handle was returned, /// waiting for the leader transaction if request collapsed, and returns a cache handle. - cache-busy-handle-wait: func( - handle: busy-handle, - ) -> result; - - /// Insert an object into the cache with the given metadata. - /// - /// Can only be used in if the cache handle state includes the `must-insert-or-update` flag. - /// - /// The returned handle is to a streaming body that is used for writing the object into - /// the cache. - transaction-insert: func( - handle: handle, - mask: write-options-mask, - options: write-options, - ) -> result; - - /// Insert an object into the cache with the given metadata, and return a readable stream of the - /// bytes as they are stored. - /// - /// This helps avoid the "slow reader" problem on a teed stream, for example when a program wishes - /// to store a backend request in the cache while simultaneously streaming to a client in an HTTP - /// response. - /// - /// The returned body handle is to a streaming body that is used for writing the object _into_ - /// the cache. The returned cache handle provides a separate transaction for reading out the - /// newly cached object to send elsewhere. - transaction-insert-and-stream-back: func( - handle: handle, - mask: write-options-mask, - options: write-options, - ) -> result, error>; - - /// Update the metadata of an object in the cache without changing its data. - /// - /// Can only be used in if the cache handle state includes both of the flags: - /// - `found` - /// - `must-insert-or-update` - transaction-update: func( - handle: handle, - mask: write-options-mask, - options: write-options, - ) -> result<_, error>; - - /// Cancel an obligation to provide an object to the cache. - /// - /// Useful if there is an error before streaming is possible, e.g. if a backend is unreachable. - transaction-cancel: func(handle: handle) -> result<_, error>; + await-entry: func( + handle: pending-entry, + ) -> result; - /// Close an interaction with the cache that has not yet finished request collapsing. - close-busy: func(handle: busy-handle) -> result<_, error>; + /// Closes an interaction with the cache that has not yet finished request collapsing. + close-pending-entry: func(handle: pending-entry) -> result<_, error>; - /// Close an ongoing interaction with the cache. + /// Closes an ongoing interaction with the cache. /// /// If the cache handle state includes the `must-insert-or-update` (and hence no insert or /// update has been performed), closing the handle cancels any request collapsing, potentially /// choosing a new waiter to perform the insertion/update. - close: func(handle: handle) -> result<_, error>; - - get-state: func(handle: handle) -> result; - - /// Gets the user metadata of the found object, returning None if no object - /// was found. - get-user-metadata: func(handle: handle, max-len: u64) -> result>, error>; - - /// Gets a range of the found object body, returning the `optional-none` error if there - /// was no found object. - /// - /// The returned `body_handle` must be closed before calling this function again on the same - /// `cache_handle`. /// - /// Note: until the CacheD protocol is adjusted to fully support this functionality, - /// the body of objects that are past the stale-while-revalidate period will not - /// be available, even when other metadata is. - get-body: func( - handle: handle, - mask: get-body-options-mask, - options: get-body-options, - ) -> result; - - /// Gets the content length of the found object, returning the `$none` error if there - /// was no found object, or no content length was provided. - get-length: func(handle: handle) -> result; - - /// Gets the configured max age of the found object, returning the `$none` error if there - /// was no found object. - get-max-age-ns: func(handle: handle) -> result; - - /// Gets the configured stale-while-revalidate period of the found object, returning the - /// `$none` error if there was no found object. - get-stale-while-revalidate-ns: func(handle: handle) -> result; - - /// Gets the age of the found object, returning the `$none` error if there - /// was no found object. - get-age-ns: func(handle: handle) -> result; - - /// Gets the number of cache hits for the found object, returning the `$none` error if there - /// was no found object. - get-hits: func(handle: handle) -> result; - - flags replace-options-mask { - reserved, - request-headers, - replace-strategy, - service-id, - always-use-requested-range, - } + /// This may be passed either an `entry` or a `replace-entry`. + close: func(handle: entry) -> result<_, error>; - /// Extensible options for cache replace operations + /// Options for cache replace operations record replace-options { /// a full request handle, but used only for its headers - request-headers: request-handle, - replace-strategy: replace-strategy, - service-id: string, + request-headers: option>, + replace-strategy: option, + service-id: option, + always-use-requested-range: bool, + + /// Additional options may be added in the future via this resource type. + extra: option>, } + /// Extensibility for `replace-options` + resource extra-replace-options {} + enum replace-strategy { + /// Immediately start the replace and do not wait for any other pending requests for the same + /// object, including insert requests. + /// + /// With this strategy a replace will race all other pending requests to update the object. + /// + /// The existing object will be accessible until this replace finishes providing the replacement + /// object. + /// + /// This is the default replace strategy. immediate, + + /// Immediate, but remove the existing object immediately + /// + /// Requests for the same object that arrive after this replace starts will wait until this + /// replace starts providing the replacement object. immediate-force-miss, + + /// Join the wait list behind other pending requests before starting this request. + /// + /// With this strategy this replace request will wait for an in-progress replace or insert + /// request before starting. + /// + /// This strategy allows implementing a counter, but may cause timeouts if too many requests + /// are waiting for in-progress and waiting updates to complete. wait, } - - /// The entrypoint to the replace API. - /// - /// This operation always participates in request collapsing and may return stale objects. - replace: func( - key: list, - mask: replace-options-mask, - options: replace-options, - ) -> result; - - /// Replace an object in the cache with the given metadata - /// - /// The returned handle is to a streaming body that is used for writing the object into - /// the cache. - replace-insert: func( - h: cache-replace-handle, - mask: write-options-mask, - options: write-options, - ) -> result; - - /// Gets the age of the existing object during replace, returning - /// `None` if there was no object. - replace-get-age-ns: func( - h: cache-replace-handle, - ) -> result, error>; - - /// Gets a range of the existing object body, returning `None` if there - /// was no existing object. - /// - /// The returned `body_handle` must be closed before calling this function - /// again on the same `cache_replace_handle`. - replace-get-body: func( - h: cache-replace-handle, - mask: get-body-options-mask, - options: get-body-options, - ) -> result, error>; - - /// Gets the number of cache hits for the existing object during replace, - /// returning `None` if there was no object. - replace-get-hits: func( - h: cache-replace-handle, - ) -> result, error>; - - /// Gets the content length of the existing object during replace, - /// returning `None` if there was no object, or no content - /// length was provided. - replace-get-length: func( - h: cache-replace-handle, - ) -> result, error>; - - /// Gets the configured max age of the existing object during replace, - /// returning the `$none` error if there was no object. - replace-get-max-age-ns: func( - h: cache-replace-handle, - ) -> result, error>; - - /// Gets the configured stale-while-revalidate period of the existing - /// object during replace, returning the `$none` error if there was no - /// object. - replace-get-stale-while-revalidate-ns: func( - h: cache-replace-handle, - ) -> result, error>; - - /// Gets the lookup state of the existing object during replace, returning - /// the `$none` error if there was no object. - replace-get-state: func( - h: cache-replace-handle, - ) -> result, error>; - - /// Gets the user metadata of the existing object during replace, returning - /// the `$none` error if there was no object. - replace-get-user-metadata: func( - h: cache-replace-handle, - max-len: u64, - ) -> result>, error>; - } -/// Proposed hostcall interface for the HTTP Cache API +/// [HTTP Cache] API. /// /// Overall, this should look very familiar to users of the Core Cache API. The primary differences /// are: /// -/// - HTTP `request_handle`s and `response_handle`s are used rather than relying on the user to -/// encode headers, status codes, etc in `user_metadata`. +/// - HTTP `request`s and `response`s are used rather than relying on the user to +/// encode headers, status codes, etc in `user-metadata`. /// -/// - Convenience functions specific to HTTP semantics are provided, such as `is_request_cacheable`, -/// `get_suggested_backend_request`, `get_suggested_cache_options`, and -/// `transaction_record_not_cacheable`. +/// - Convenience functions specific to HTTP semantics are provided, such as `is-request-cacheable`, +/// `get-suggested-backend-request`, `get-suggested-write-options`, and +/// `transaction-record-not-cacheable`. /// /// The HTTP-specific behavior of these functions is intended to support applications that match the -/// normative guidance in RFC 9111. For example, `is_request_cacheable` returns `false` for `POST` +/// normative guidance in [RFC 9111]. For example, `is-request-cacheable` returns `false` for `POST` /// requests. However, this answer along with those of many of these functions explicitly provide -/// _suggestions_; they do not necessarily need to be followed if custom behavior is required, such +/// *suggestions*; they do not necessarily need to be followed if custom behavior is required, such /// as caching `POST` responses when the application author knows that to be safe. /// -/// The starting points for this API are `lookup` (no request collapsing) and `transaction_lookup` +/// The starting points for this API are `lookup` (no request collapsing) and `transaction-lookup` /// (request collapsing). +/// +/// [HTTP Cache]: https://www.fastly.com/documentation/guides/concepts/edge-state/cache/cache-freshness/ +/// [RFC 9111]: https://www.rfc-editor.org/rfc/rfc9111.html interface http-cache { use types.{error}; - use http-types.{request-handle, response-handle, body-handle}; + use http-body.{body}; + use http-req.{request}; + use http-resp.{response}; use cache.{lookup-state, object-length, duration-ns, cache-hit-count}; - /// A handle to an HTTP Cache transaction. - type cache-handle = u32; + /// An HTTP Cache transaction. + resource entry { + /// (DEPRECATED) Use transaction-lookup + lookup: static func( + req-handle: borrow, + options: lookup-options, + ) -> result; + + /// Performs a cache lookup based on the given request. + /// + /// This operation always participates in request collapsing and may return an obligation to + /// insert or update responses, and/or stale responses. To bypass request collapsing, use + /// `lookup` instead. + /// + /// The request is not consumed. + transaction-lookup: static func( + req-handle: borrow, + options: lookup-options, + ) -> result; + + /// Inserts a response into the cache with the given options, returning a streaming body handle + /// that is ready for writing or appending. + /// + /// Can only be used if the cache handle state includes the `must-insert-or-update` flag. + /// + /// The response is consumed. + transaction-insert: func( + resp-handle: response, + options: write-options, + ) -> result; + + /// Inserts a response into the cache with the given options, and return a fresh cache handle + /// that can be used to retrieve and stream the response while it's being inserted. + /// + /// This helps avoid the “slow reader” problem on a teed stream, for example when a program + /// wishes to store a backend request in the cache while simultaneously streaming to a client + /// in an HTTP response. + /// + /// The response is consumed. + transaction-insert-and-stream-back: func( + resp-handle: response, + options: write-options, + ) -> result, error>; + + /// Updates freshness lifetime, response headers, and caching settings without updating the + /// response body. + /// + /// Can only be used in if the cache handle state includes both of the flags: + /// - `found` + /// - `must-insert-or-update` + /// + /// The response is consumed. + transaction-update: func( + resp-handle: response, + options: write-options, + ) -> result<_, error>; + + /// Updates freshness lifetime, response headers, and caching settings without updating the + /// response body, and return a fresh cache handle that can be used to retrieve and stream the + /// stored response. + /// + /// Can only be used in if the cache handle state includes both of the flags: + /// - `found` + /// - `must-insert-or-update` + /// + /// The response is consumed. + transaction-update-and-return-fresh: func( + resp-handle: response, + options: write-options, + ) -> result; + + /// Disables request collapsing and response caching for this cache entry. + /// + /// In Varnish terms, this function stores a hit-for-pass object. + /// + /// Only the max age and, optionally, the vary rule are read from the `options` + /// for this function. + transaction-record-not-cacheable: func( + options: write-options, + ) -> result<_, error>; + + /// Prepares a suggested request to make to a backend to satisfy the looked-up request. + /// + /// If there is a stored, stale response, this suggested request may be for revalidation. If the + /// looked-up request is ranged, the suggested request will be unranged in order to try caching + /// the entire response. + get-suggested-backend-request: func() -> result; + + /// Prepares a suggested set of cache write options for a given request and response pair. + /// + /// The response is not consumed. + get-suggested-write-options: func( + response: borrow, + ) -> result; + + /// Adjusts a response into the appropriate form for storage and provides a storage action + /// recommendation. + /// + /// For example, if the looked-up request contains conditional headers, this function will + /// interpret a `304 Not Modified` response for revalidation by updating headers. + /// + /// In addition to the updated response, this function returns the recommended storage action. + prepare-response-for-storage: func( + response: borrow, + ) -> result, error>; + + /// Retrieves a stored response from the cache, returning the `error.optional-none` error if + /// there was no response found. + /// + /// If `transform-for-client` is set, the response will be adjusted according to the looked-up + /// request. For example, a response retrieved for a range request may be transformed into a + /// `206 Partial Content` response with an appropriate `content-range` header. + get-found-response: func( + transform-for-client: u32, + ) -> result, error>; + + /// Gets the state of a cache transaction. + /// + /// Primarily useful after performing the lookup to determine what subsequent operations are + /// possible and whether any insertion or update obligations exist. + get-state: func( + ) -> result; + + /// Gets the length of the found response, returning the `error.optional-none` error if there + /// was no response found or no length was provided. + get-length: func() -> result; + + /// Gets the configured max age of the found response in nanoseconds, returning the + /// `error.optional-none` error if there was no response found. + get-max-age-ns: func() -> result; + + /// Gets the configured stale-while-revalidate period of the found response in nanoseconds, + /// returning the `error.optional-none` error if there was no response found. + get-stale-while-revalidate-ns: func( + ) -> result; + + /// Gets the age of the found response in nanoseconds, returning the `error.optional-none` error + /// if there was no response found. + get-age-ns: func() -> result; + + /// Gets the number of cache hits for the found response, returning the `error.optional-none` + /// error if there was no response found. + /// + /// This figure only reflects hits for a stored response in a particular cache server + /// or cluster, not the entire Fastly network. + get-hits: func() -> result; + + /// Gets whether a found response is marked as containing sensitive data, returning the + /// `error.optional-none` error if there was no response found. + get-sensitive-data: func() -> result; + + /// Gets the surrogate keys of the found response, returning the `error.optional-none` error if + /// there was no response found. + /// + /// The output is a list of surrogate keys separated by spaces. + /// + /// If the full list requires more than `max-len` bytes, an `error.buffer-len` + /// error is returned containing the required size. + get-surrogate-keys: func( + max-len: u64, + ) -> result; + + /// Gets the vary rule of the found response, returning the `error.optional-none` error if there + /// was no response found. + /// + /// The output is a list of header names separated by spaces. + /// + /// If the full list requires more than `max-len` bytes, an `error.buffer-len` + /// error is returned containing the required size. + get-vary-rule: func( + max-len: u64, + ) -> result; + + /// Abandons an obligation to provide a response to the cache. + /// + /// Useful if there is an error before streaming is possible, for example if a backend is + /// unreachable. + /// + /// If there are other requests collapsed on this transaction, one of those other requests will + /// be awoken and given the obligation to provide a response. If subsequent requests + /// are unlikely to yield cacheable responses, this may lead to undesired serialization of + /// requests. Consider using `transaction-record-not-cacheable` to make lookups for this request + /// bypass the cache. + transaction-abandon: func() -> result<_, error>; + } /// The suggested action to take for spec-recommended behavior following - /// `prepare_response_for_storage`. + /// `prepare-response-for-storage`. enum storage-action { - /// Insert the response into cache (`transaction_insert*`). + /// Insert the response into cache (for `transaction-insert` and + /// `transaction-insert-and-stream-back`). insert, - /// Update the stale response in cache (`transaction_update*`). + /// Update the stale response in cache (for `transaction-update` and + /// `transaction-update-and-return-fresh`). update, /// Do not store this response. do-not-store, /// Do not store this response, and furthermore record its non-cacheability for other pending - /// requests (`transaction_record_not_cacheable`). + /// requests (`transaction-record-not-cacheable`). record-uncacheable, } /// Non-required options for cache lookups. - /// - /// This record is always provided along with an `http_cache_lookup_options_mask` value that - /// indicates which of the fields in this record are valid. - record cache-lookup-options { + record lookup-options { /// Cache key to use in lieu of the automatically-generated cache key based on the request's /// properties. - override-key: list, - } + override-key: option>, + /// Backend name that will be used for the eventual request. + backend-name: option, - /// Options mask for `http_cache_lookup_options`. - flags cache-lookup-options-mask { - reserved, - override-key, + /// Additional options may be added in the future via this resource type. + extra: option>, } - flags cache-write-options-mask { - reserved, - vary-rule, - initial-age-ns, - stale-while-revalidate-ns, - surrogate-keys, - length, - sensitive-data, - } + /// Extensibility for `lookup-options` + resource extra-lookup-options {} /// Options for cache insertions and updates. - /// - /// This record is always provided along with an `http_cache_write_options_mask` value that - /// indicates which of the fields in this record are valid. - record cache-write-options { + record write-options { /// The maximum age of the response before it is considered stale, in nanoseconds. /// - /// This field is required; there is no flag for it in `http_cache_write_options_mask`. + /// This field is required. max-age-ns: duration-ns, /// A list of header names to use when calculating variants for this response. /// /// The format is a string containing header names separated by spaces. - vary-rule: string, + vary-rule: option, /// The initial age of the response in nanoseconds. /// @@ -1591,23 +2407,23 @@ interface http-cache { /// /// This age is used to determine the freshness lifetime of the response as well as to /// prioritize which variant to return if a subsequent lookup matches more than one vary rule - initial-age-ns: duration-ns, + initial-age-ns: option, - /// The maximum duration after `max_age` during which the response may be delivered stale + /// The maximum duration after `max-age` during which the response may be delivered stale /// while being revalidated, in nanoseconds. /// /// If this field is not set, the default value is zero. - stale-while-revalidate-ns: duration-ns, + stale-while-revalidate-ns: option, /// A list of surrogate keys that may be used to purge this response. /// - /// The format is a string containing [valid surrogate - /// keys](https://www.fastly.com/documentation/reference/http/http-headers/Surrogate-Key/) - /// separated by spaces. + /// The format is a string containing [valid surrogate keys] separated by spaces. /// /// If this field is not set, no surrogate keys will be associated with the response. This /// means that the response cannot be purged except via a purge-all operation. - surrogate-keys: string, + /// + /// [valid surrogate keys]: https://www.fastly.com/documentation/reference/http/http-headers/Surrogate-Key/ + surrogate-keys: option, /// The length of the response body. /// @@ -1616,341 +2432,146 @@ interface http-cache { /// When possible, this field should be set so that other clients waiting to retrieve the /// body have enough information to synthesize a `content-length` even before the complete /// body is inserted to the cache. - length: object-length, + length: option, + + /// Enable or disable PCI/HIPAA-compliant non-volatile caching. + /// + /// See the [Fastly PCI-Compliant Caching and Delivery documentation] for details. + /// + /// [Fastly PCI-Compliant Caching and Delivery documentation]: https://docs.fastly.com/products/pci-compliant-caching-and-delivery + sensitive-data: bool, + + /// Additional options may be added in the future via this resource type. + extra: option>, } - /// Determine whether a request is cacheable per conservative RFC 9111 semantics. + /// Extensibility for `write-options` + resource extra-write-options {} + + /// Determines whether a request is cacheable per conservative [RFC 9111] semantics. /// /// In particular, this function checks whether the request method is `GET` or `HEAD`, and /// considers requests with other methods uncacheable. Applications where it is safe to cache /// responses to other methods should consider using their own cacheability check instead of /// this function. - is-request-cacheable: func(req-handle: request-handle) -> result; + /// + /// [RFC 9111]: https://www.rfc-editor.org/rfc/rfc9111.html + is-request-cacheable: func(request: borrow) -> result; /// Retrieves the default cache key for the request. /// - /// The `$key_out` parameter must point to an array of size `key_out_len`. - /// - /// If the guest-provided output parameter is not long enough to contain the full key, - /// the required size is written by the host to `nwritten_out` and the `$buflen` - /// error is returned. + /// If the full key requires more than `max-len` bytes, an `error.buffer-len` + /// error is returned containing the required size. /// /// At the moment, HTTP cache keys must always be 32 bytes. get-suggested-cache-key: func( - req-handle: request-handle, + request: borrow, max-len: u64, ) -> result, error>; - /// Perform a cache lookup based on the given request without participating in request - /// collapsing. - /// - /// The request is not consumed. - lookup: func( - req-handle: request-handle, - options-mask: cache-lookup-options-mask, - options: cache-lookup-options, - ) -> result; - - /// Perform a cache lookup based on the given request. - /// - /// This operation always participates in request collapsing and may return an obligation to - /// insert or update responses, and/or stale responses. To bypass request collapsing, use - /// `lookup` instead. - /// - /// The request is not consumed. - transaction-lookup: func( - req-handle: request-handle, - options-mask: cache-lookup-options-mask, - options: cache-lookup-options, - ) -> result; - - /// Insert a response into the cache with the given options, returning a streaming body handle - /// that is ready for writing or appending. - /// - /// Can only be used if the cache handle state includes the `$must_insert_or_update` flag. - /// - /// The response is consumed. - transaction-insert: func( - handle: cache-handle, - resp-handle: response-handle, - options-mask: cache-write-options-mask, - options: cache-write-options, - ) -> result; - - /// Insert a response into the cache with the given options, and return a fresh cache handle - /// that can be used to retrieve and stream the response while it's being inserted. - /// - /// This helps avoid the "slow reader" problem on a teed stream, for example when a program wishes - /// to store a backend request in the cache while simultaneously streaming to a client in an HTTP - /// response. - /// - /// The response is consumed. - transaction-insert-and-stream-back: func( - handle: cache-handle, - resp-handle: response-handle, - options-mask: cache-write-options-mask, - options: cache-write-options, - ) -> result, error>; - - /// Update freshness lifetime, response headers, and caching settings without updating the - /// response body. - /// - /// Can only be used in if the cache handle state includes both of the flags: - /// - `$found` - /// - `$must_insert_or_update` - /// - /// The response is consumed. - transaction-update: func( - handle: cache-handle, - resp-handle: response-handle, - options-mask: cache-write-options-mask, - options: cache-write-options, - ) -> result<_, error>; - - /// Update freshness lifetime, response headers, and caching settings without updating the - /// response body, and return a fresh cache handle that can be used to retrieve and stream the - /// stored response. - /// - /// Can only be used in if the cache handle state includes both of the flags: - /// - `$found` - /// - `$must_insert_or_update` - /// - /// The response is consumed. - transaction-update-and-return-fresh: func( - handle: cache-handle, - resp-handle: response-handle, - options-mask: cache-write-options-mask, - options: cache-write-options, - ) -> result; - - /// Disable request collapsing and response caching for this cache entry. - /// - /// In Varnish terms, this function stores a hit-for-pass object. - /// - /// Only the max age and, optionally, the vary rule are read from the options mask and struct - /// for this function. - transaction-record-not-cacheable: func( - handle: cache-handle, - options-mask: cache-write-options-mask, - options: cache-write-options, - ) -> result<_, error>; - - /// Abandon an obligation to provide a response to the cache. - /// - /// Useful if there is an error before streaming is possible, e.g. if a backend is unreachable. - /// - /// If there are other requests collapsed on this transaction, one of those other requests will - /// be awoken and given the obligation to provide a response. Note that if subsequent requests - /// are unlikely to yield cacheable responses, this may lead to undesired serialization of - /// requests. Consider using `transaction_record_not_cacheable` to make lookups for this request - /// bypass the cache. - transaction-abandon: func( - handle: cache-handle, - ) -> result<_, error>; - - /// Close an ongoing interaction with the cache. + /// Closes an ongoing interaction with the cache. /// - /// If the cache handle state includes `$must_insert_or_update` (and hence no insert or update + /// If the cache handle state includes `must-insert-or-update` (and hence no insert or update /// has been performed), closing the handle cancels any request collapsing, potentially choosing /// a new waiter to perform the insertion/update. close: func( - handle: cache-handle, + handle: entry, ) -> result<_, error>; - /// Prepare a suggested request to make to a backend to satisfy the looked-up request. - /// - /// If there is a stored, stale response, this suggested request may be for revalidation. If the - /// looked-up request is ranged, the suggested request will be unranged in order to try caching - /// the entire response. - get-suggested-backend-request: func( - handle: cache-handle, - ) -> result; - - resource suggested-cache-options { - max-age-ns: func() -> duration-ns; - vary-rule: func(max-len: u64) -> result, error>; - initial-age-ns: func() -> duration-ns; - stale-while-revalidate-ns: func() -> duration-ns; - surrogate-keys: func(max-len: u64) -> result, error>; - length: func() -> option; - sensitive-data: func() -> bool; + /// The methods in this resource return values that correspond to the fields in a + /// `write-options`. This type is used when a `write-options` value would + /// be returned, so that it can use `max-len` parameters when returning + /// dynamically-sized data, and so that it excludes the `extra` field, since borrowed + /// handles cannot be returned from functions. + resource suggested-write-options { + /// Returns the suggested value for the `write-options.max-age-ns` field. + get-max-age-ns: func() -> duration-ns; + /// Returns the suggested value for the `write-options.vary-rule` field. + get-vary-rule: func(max-len: u64) -> result; + /// Returns the suggested value for the `write-options.initial-age-ns` field. + get-initial-age-ns: func() -> duration-ns; + /// Returns the suggested value for the `write-options.stale-while-revalidate-ns` field. + get-stale-while-revalidate-ns: func() -> duration-ns; + /// Returns the suggested value for the `write-options.surrogate-keys` field. + get-surrogate-keys: func(max-len: u64) -> result; + /// Returns the suggested value for the `write-options.length` field. + get-length: func() -> option; + /// Returns the suggested value for the `write-options.sensitive-data` field. + get-sensitive-data: func() -> bool; } - - /// Prepare a suggested set of cache write options for a given request and response pair. - /// - /// The ABI of this function includes several unusual types of input and output parameters. - /// - /// The bits set in the `options_mask` input parameter describe which cache options the guest is - /// requesting that the host provide. - /// - /// The `options` input parameter allows the guest to provide output parameters for - /// pointer/length options. When the corresponding bit is set in `options_mask`, the pointer and - /// length should be set in this record to be used by the host to provide the output. - /// - /// The `options_mask_out` output parameter is only used by the host to indicate the status of - /// pointer/length data in the `options_out` record. The flag for a given pointer/length - /// parameter is set by the host if the corresponding flag was set in `options_mask`, and the - /// value is present in the suggested options. If the host returns a status of `$buflen`, the - /// same set of flags will be set, but the length value of the corresponding fields in - /// `options_out` are set to the lengths that would be required to read the full value from the - /// host on a subsequent call. - /// - /// The `options_out` output parameter is where the host writes the suggested options that were - /// requested by the guest in `options_mask`. For pointer/length data, if there was enough room - /// to write the suggested option, the length field will contain the length of the data actually - /// written, while the pointer field will match the input pointer. - /// - /// The response is not consumed. - get-suggested-cache-options: func( - handle: cache-handle, - response: response-handle, - ) -> result; - - /// Adjust a response into the appropriate form for storage and provides a storage action recommendation. - /// - /// For example, if the looked-up request contains conditional headers, this function will - /// interpret a `304 Not Modified` response for revalidation by updating headers. - /// - /// In addition to the updated response, this function returns the recommended storage action. - prepare-response-for-storage: func( - handle: cache-handle, - response: response-handle, - ) -> result, error>; - - /// Retrieve a stored response from the cache, returning the `$none` error if there was no found - /// response. - /// - /// If `transform_for_client` is set, the response will be adjusted according to the looked-up - /// request. For example, a response retrieved for a range request may be transformed into a - /// `206 Partial Content` response with an appropriate `content-range` header. - get-found-response: func( - handle: cache-handle, - transform-for-client: u32, - ) -> result, error>; - - /// Get the state of a cache transaction. - /// - /// Primarily useful after performing the lookup to determine what subsequent operations are - /// possible and whether any insertion or update obligations exist. - get-state: func( - handle: cache-handle, - ) -> result; - - /// Get the length of the found response, returning the `$none` error if there was no found - /// response or no length was provided. - get-length: func(handle: cache-handle) -> result; - - /// Get the configured max age of the found response in nanoseconds, returning the `$none` error - /// if there was no found response. - get-max-age-ns: func(handle: cache-handle) -> result; - - /// Get the configured stale-while-revalidate period of the found response in nanoseconds, - /// returning the `$none` error if there was no found response. - get-stale-while-revalidate-ns: func( - handle: cache-handle, - ) -> result; - - /// Get the age of the found response in nanoseconds, returning the `$none` error if there was - /// no found response. - get-age-ns: func(handle: cache-handle) -> result; - - /// Get the number of cache hits for the found response, returning the `$none` error if there - /// was no found response. - /// - /// Note that this figure only reflects hits for a stored response in a particular cache server - /// or cluster, not the entire Fastly network. - get-hits: func(handle: cache-handle) -> result; - - /// Get whether a found response is marked as containing sensitive data, returning the `$none` - /// error if there was no found response. - get-sensitive-data: func(handle: cache-handle) -> result; - - /// Get the surrogate keys of the found response, returning the `$none` error if there was no - /// found response. - /// - /// The output is a list of surrogate keys separated by spaces. - /// - /// If the guest-provided output parameter is not long enough to contain the full list of - /// surrogate keys, the required size is written by the host to `nwritten_out` and the `$buflen` - /// error is returned. - get-surrogate-keys: func( - handle: cache-handle, - max-len: u64, - ) -> result, error>; - - /// Get the vary rule of the found response, returning the `$none` error if there was no found - /// response. - /// - /// The output is a list of header names separated by spaces. - /// - /// If the guest-provided output parameter is not long enough to contain the full list of - /// surrogate keys, the required size is written by the host to `nwritten_out` and the `$buflen` - /// error is returned. - get-vary-rule: func( - handle: cache-handle, - max-len: u64, - ) -> result, error>; } +/// [Config Store] API. +/// +/// [Config Store]: https://www.fastly.com/documentation/guides/concepts/edge-state/dynamic-config/#config-stores interface config-store { use types.{error}; - /// A handle to an Config Store. - type handle = u32; - - /// Attempt to open the named config store. - open: func(name: string) -> result; - - /// Fetch a value from the config store, returning `None` if it doesn't exist. - get: func( - store: handle, - key: string, - max-len: u64, - ) -> result, error>; + /// A Config Store. + resource store { + /// Attempts to open the named config store. + /// + /// Names are case sensitive. + open: static func(name: string) -> result; + + /// Fetches a value from the config store, returning `none` if it doesn't exist. + get: func( + key: string, + max-len: u64, + ) -> result, error>; + } } +/// [Shielding] API. +/// +/// [Shielding]: https://www.fastly.com/documentation/guides/concepts/shielding/ interface shielding { use types.{error}; shield-info: func( name: string, max-len: u64, - ) -> result, error>; - - flags shield-backend-options-mask { - reserved, - cache-key, - } + ) -> result; record shield-backend-options { - cache-key: string, + cache-key: option, + + /// Additional options may be added in the future via this resource type. + extra: option>, } + /// Extensibility for `shield-backend-options` + resource extra-shield-backend-options {} + backend-for-shield: func( name: string, - options-mask: shield-backend-options-mask, options: shield-backend-options, max-len: u64, ) -> result; } -/// Fastly Image Optimizer +/// [Image Optimizer] API. +/// +/// [Image Optimizer]: https://www.fastly.com/documentation/guides/full-site-delivery/image-optimization/about-fastly-image-optimizer/ interface image-optimizer { - use http-types.{body-handle, request-handle, response}; + use http-body.{body}; + use http-req.{request}; + use http-resp.{response-with-body}; use types.{error}; - flags image-optimizer-transform-config-options { - reserved, - sdk-claims-opts, - } - - record image-optimizer-transform-config { + record image-optimizer-transform-options { /// Contains any Image Optimizer API parameters that were set /// as well as the Image Optimizer region the request is meant for. - sdk-claims-opts: string, + sdk-claims-opts: option, + + /// Additional options may be added in the future via this resource type. + extra: option>, } + /// Extensibility for `image-optimizer-transform-options` + resource extra-image-optimizer-transform-options {} + enum image-optimizer-error-tag { uninitialized, ok, @@ -1964,57 +2585,81 @@ interface image-optimizer { } transform-image-optimizer-request: func( - origin-image-request: request-handle, - origin-image-request-body: body-handle, + origin-image-request: borrow, + origin-image-request-body: option, origin-image-request-backend: string, - io-transform-config-mask: image-optimizer-transform-config-options, - io-transform-config: image-optimizer-transform-config, + io-transform-options: image-optimizer-transform-options, io-error-detail: image-optimizer-error-detail, - ) -> result; + ) -> result; } - -interface reactor { - use http-types.{request-handle, body-handle}; - - /// Serve the given request - /// - /// response handle not currently returned, because in the case of a streamed response - /// send downstream must be fully streamed due to the run to completion semantics. - serve: func(req: request-handle, body: body-handle) -> result; +/// The exported interface. +/// +/// The `handle` function serves as the main entrypoint to applications. Unlike the +/// rest of the interfaces in this package, this `http-incoming` interface is exported by +/// applications rather than imported, which means that this is a function defined +/// by the application and called from the outside, rather than a function called +/// by the application into the outside. +interface http-incoming { + use http-body.{body}; + use http-req.{request}; + + /// Handle the given request. + /// + /// Conceptually, `send` returns a response to the given request, however this isn't + /// modeled as a literal return value in this API. Instead, the `send-downstream` + /// function is used to send the response. This allows for the option of streaming the + /// response body, since that requires the program to continue executing after the + /// response has been initiated. + handle: func(request: request, body: body) -> result; } +/// Features for interacting with the Compute runtime. interface compute-runtime { - use types.{error}; - + /// A timestamp in milliseconds. type vcpu-ms = u64; - get-vcpu-ms: func() -> result; + /// Gets the amount of vCPU time that has passed since this instance was started, in milliseconds. + /// + /// This function returns only time spent running on a vCPU, and does not include time spent + /// performing any I/O operations. However, it is based on clock time passing, and so will include + /// time spent executing hostcalls, is heavily affected by what core of what CPU is running the + /// code, and can even be influenced by the state of the CPU. + /// + /// As a result, this function *should not be used in benchmarking across runs*. It can be used, + /// with caution, to compare the runtime of different operations within the same session. + get-vcpu-ms: func() -> vcpu-ms; +} + +/// WASI interfaces used by `fastly:compute/service`. +world wasi-imports { + import wasi:clocks/wall-clock@0.2.6; + import wasi:clocks/monotonic-clock@0.2.6; + import wasi:io/error@0.2.6; + import wasi:io/streams@0.2.6; + import wasi:random/random@0.2.6; + import wasi:cli/environment@0.2.6; + import wasi:cli/exit@0.2.6; + import wasi:cli/stdout@0.2.6; + import wasi:cli/stderr@0.2.6; + import wasi:cli/stdin@0.2.6; } -world compute { - import wasi:clocks/wall-clock@0.2.0; - import wasi:clocks/monotonic-clock@0.2.0; - import wasi:io/error@0.2.0; - import wasi:io/streams@0.2.0; - import wasi:random/random@0.2.0; - import wasi:cli/environment@0.2.0; - import wasi:cli/exit@0.2.0; - import wasi:cli/stdout@0.2.0; - import wasi:cli/stderr@0.2.0; - import wasi:cli/stdin@0.2.0; - - // public interfaces +/// Custom interfaces used by `fastly:compute/service`. +world custom-imports { import acl; import async-io; import backend; import cache; import compute-runtime; + import config-store; import dictionary; import geo; import device-detection; import erl; import http-body; + import http-cache; + import http-downstream; import http-req; import http-resp; import image-optimizer; @@ -2023,12 +2668,32 @@ world compute { import object-store; import purge; import secret-store; - import config-store; - import uap; - - // experimental interfaces - import http-cache; import shielding; +} + +world custom-exports { + // Export the `http-incoming` interface. + export http-incoming; +} + +/// Interfaces that a Fastly Compute service may import. +/// +/// This contains the imports used in the `service` world, factored out into a +/// separate world so that it can be used by library components. Library components +/// are components that do not export anything themselves. +world service-imports { + include wasi-imports; + include custom-imports; +} - export reactor; +/// A Fastly Compute service. +/// +/// This defines the set of interfaces available to, and expected of, +/// Fastly Compute service applications. +/// +/// This `service` world includes all the `service-imports` imports, and adds the +/// `http-incoming` exports. +world service { + include service-imports; + include custom-exports; } diff --git a/wit/deps/filesystem/preopens.wit b/wit/deps/filesystem/preopens.wit index da801f6..f228479 100644 --- a/wit/deps/filesystem/preopens.wit +++ b/wit/deps/filesystem/preopens.wit @@ -1,8 +1,11 @@ -package wasi:filesystem@0.2.0; +package wasi:filesystem@0.2.6; +@since(version = 0.2.0) interface preopens { + @since(version = 0.2.0) use types.{descriptor}; - /// Return the set of preopened directories, and their path. + /// Return the set of preopened directories, and their paths. + @since(version = 0.2.0) get-directories: func() -> list>; } diff --git a/wit/deps/filesystem/types.wit b/wit/deps/filesystem/types.wit index 11108fc..75c1904 100644 --- a/wit/deps/filesystem/types.wit +++ b/wit/deps/filesystem/types.wit @@ -1,4 +1,4 @@ -package wasi:filesystem@0.2.0; +package wasi:filesystem@0.2.6; /// WASI filesystem is a filesystem API primarily intended to let users run WASI /// programs that access their files on their existing filesystems, without /// significant overhead. @@ -23,16 +23,21 @@ package wasi:filesystem@0.2.0; /// [WASI filesystem path resolution]. /// /// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md +@since(version = 0.2.0) interface types { - use wasi:io/streams@0.2.0.{input-stream, output-stream, error}; - use wasi:clocks/wall-clock@0.2.0.{datetime}; + @since(version = 0.2.0) + use wasi:io/streams@0.2.6.{input-stream, output-stream, error}; + @since(version = 0.2.0) + use wasi:clocks/wall-clock@0.2.6.{datetime}; /// File size or length of a region within a file. + @since(version = 0.2.0) type filesize = u64; /// The type of a filesystem object referenced by a descriptor. /// /// Note: This was called `filetype` in earlier versions of WASI. + @since(version = 0.2.0) enum descriptor-type { /// The type of the descriptor or file is unknown or is different from /// any of the other types specified. @@ -56,6 +61,7 @@ interface types { /// Descriptor flags. /// /// Note: This was called `fdflags` in earlier versions of WASI. + @since(version = 0.2.0) flags descriptor-flags { /// Read mode: Data can be read. read, @@ -77,7 +83,7 @@ interface types { /// WASI. At this time, it should be interpreted as a request, and not a /// requirement. data-integrity-sync, - /// Requests that reads be performed at the same level of integrety + /// Requests that reads be performed at the same level of integrity /// requested for writes. This is similar to `O_RSYNC` in POSIX. /// /// The precise semantics of this operation have not yet been defined for @@ -99,6 +105,7 @@ interface types { /// File attributes. /// /// Note: This was called `filestat` in earlier versions of WASI. + @since(version = 0.2.0) record descriptor-stat { /// File type. %type: descriptor-type, @@ -125,6 +132,7 @@ interface types { } /// Flags determining the method of how paths are resolved. + @since(version = 0.2.0) flags path-flags { /// As long as the resolved path corresponds to a symbolic link, it is /// expanded. @@ -132,6 +140,7 @@ interface types { } /// Open flags used by `open-at`. + @since(version = 0.2.0) flags open-flags { /// Create file if it does not exist, similar to `O_CREAT` in POSIX. create, @@ -144,9 +153,11 @@ interface types { } /// Number of hard links to an inode. + @since(version = 0.2.0) type link-count = u64; /// When setting a timestamp, this gives the value to set it to. + @since(version = 0.2.0) variant new-timestamp { /// Leave the timestamp set to its previous value. no-change, @@ -248,6 +259,7 @@ interface types { } /// File or memory access pattern advisory information. + @since(version = 0.2.0) enum advice { /// The application has no advice to give on its behavior with respect /// to the specified data. @@ -271,6 +283,7 @@ interface types { /// A 128-bit hash value, split into parts because wasm doesn't have a /// 128-bit integer type. + @since(version = 0.2.0) record metadata-hash-value { /// 64 bits of a 128-bit hash value. lower: u64, @@ -281,6 +294,7 @@ interface types { /// A descriptor is a reference to a filesystem object, which may be a file, /// directory, named pipe, special file, or other object on which filesystem /// calls may be made. + @since(version = 0.2.0) resource descriptor { /// Return a stream for reading from a file, if available. /// @@ -290,6 +304,7 @@ interface types { /// file and they do not interfere with each other. /// /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. + @since(version = 0.2.0) read-via-stream: func( /// The offset within the file at which to start reading. offset: filesize, @@ -301,6 +316,7 @@ interface types { /// /// Note: This allows using `write-stream`, which is similar to `write` in /// POSIX. + @since(version = 0.2.0) write-via-stream: func( /// The offset within the file at which to start writing. offset: filesize, @@ -311,12 +327,14 @@ interface types { /// May fail with an error-code describing why the file cannot be appended. /// /// Note: This allows using `write-stream`, which is similar to `write` with - /// `O_APPEND` in in POSIX. + /// `O_APPEND` in POSIX. + @since(version = 0.2.0) append-via-stream: func() -> result; /// Provide file advisory information on a descriptor. /// /// This is similar to `posix_fadvise` in POSIX. + @since(version = 0.2.0) advise: func( /// The offset within the file to which the advisory applies. offset: filesize, @@ -332,6 +350,7 @@ interface types { /// opened for writing. /// /// Note: This is similar to `fdatasync` in POSIX. + @since(version = 0.2.0) sync-data: func() -> result<_, error-code>; /// Get flags associated with a descriptor. @@ -340,6 +359,7 @@ interface types { /// /// Note: This returns the value that was the `fs_flags` value returned /// from `fdstat_get` in earlier versions of WASI. + @since(version = 0.2.0) get-flags: func() -> result; /// Get the dynamic type of a descriptor. @@ -352,12 +372,14 @@ interface types { /// /// Note: This returns the value that was the `fs_filetype` value returned /// from `fdstat_get` in earlier versions of WASI. + @since(version = 0.2.0) get-type: func() -> result; /// Adjust the size of an open file. If this increases the file's size, the /// extra bytes are filled with zeros. /// /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + @since(version = 0.2.0) set-size: func(size: filesize) -> result<_, error-code>; /// Adjust the timestamps of an open file or directory. @@ -365,6 +387,7 @@ interface types { /// Note: This is similar to `futimens` in POSIX. /// /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + @since(version = 0.2.0) set-times: func( /// The desired values of the data access timestamp. data-access-timestamp: new-timestamp, @@ -383,6 +406,7 @@ interface types { /// In the future, this may change to return a `stream`. /// /// Note: This is similar to `pread` in POSIX. + @since(version = 0.2.0) read: func( /// The maximum number of bytes to read. length: filesize, @@ -399,6 +423,7 @@ interface types { /// In the future, this may change to take a `stream`. /// /// Note: This is similar to `pwrite` in POSIX. + @since(version = 0.2.0) write: func( /// Data to write buffer: list, @@ -415,6 +440,7 @@ interface types { /// This always returns a new stream which starts at the beginning of the /// directory. Multiple streams may be active on the same directory, and they /// do not interfere with each other. + @since(version = 0.2.0) read-directory: func() -> result; /// Synchronize the data and metadata of a file to disk. @@ -423,11 +449,13 @@ interface types { /// opened for writing. /// /// Note: This is similar to `fsync` in POSIX. + @since(version = 0.2.0) sync: func() -> result<_, error-code>; /// Create a directory. /// /// Note: This is similar to `mkdirat` in POSIX. + @since(version = 0.2.0) create-directory-at: func( /// The relative path at which to create the directory. path: string, @@ -442,6 +470,7 @@ interface types { /// modified, use `metadata-hash`. /// /// Note: This was called `fd_filestat_get` in earlier versions of WASI. + @since(version = 0.2.0) stat: func() -> result; /// Return the attributes of a file or directory. @@ -451,6 +480,7 @@ interface types { /// discussion of alternatives. /// /// Note: This was called `path_filestat_get` in earlier versions of WASI. + @since(version = 0.2.0) stat-at: func( /// Flags determining the method of how the path is resolved. path-flags: path-flags, @@ -464,6 +494,7 @@ interface types { /// /// Note: This was called `path_filestat_set_times` in earlier versions of /// WASI. + @since(version = 0.2.0) set-times-at: func( /// Flags determining the method of how the path is resolved. path-flags: path-flags, @@ -477,7 +508,12 @@ interface types { /// Create a hard link. /// + /// Fails with `error-code::no-entry` if the old path does not exist, + /// with `error-code::exist` if the new path already exists, and + /// `error-code::not-permitted` if the old path is not a file. + /// /// Note: This is similar to `linkat` in POSIX. + @since(version = 0.2.0) link-at: func( /// Flags determining the method of how the path is resolved. old-path-flags: path-flags, @@ -491,12 +527,6 @@ interface types { /// Open a file or directory. /// - /// The returned descriptor is not guaranteed to be the lowest-numbered - /// descriptor not currently open/ it is randomized to prevent applications - /// from depending on making assumptions about indexes, since this is - /// error-prone in multi-threaded contexts. The returned descriptor is - /// guaranteed to be less than 2**31. - /// /// If `flags` contains `descriptor-flags::mutate-directory`, and the base /// descriptor doesn't have `descriptor-flags::mutate-directory` set, /// `open-at` fails with `error-code::read-only`. @@ -507,6 +537,7 @@ interface types { /// `error-code::read-only`. /// /// Note: This is similar to `openat` in POSIX. + @since(version = 0.2.0) open-at: func( /// Flags determining the method of how the path is resolved. path-flags: path-flags, @@ -524,6 +555,7 @@ interface types { /// filesystem, this function fails with `error-code::not-permitted`. /// /// Note: This is similar to `readlinkat` in POSIX. + @since(version = 0.2.0) readlink-at: func( /// The relative path of the symbolic link from which to read. path: string, @@ -534,6 +566,7 @@ interface types { /// Return `error-code::not-empty` if the directory is not empty. /// /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + @since(version = 0.2.0) remove-directory-at: func( /// The relative path to a directory to remove. path: string, @@ -542,6 +575,7 @@ interface types { /// Rename a filesystem object. /// /// Note: This is similar to `renameat` in POSIX. + @since(version = 0.2.0) rename-at: func( /// The relative source path of the file or directory to rename. old-path: string, @@ -557,6 +591,7 @@ interface types { /// `error-code::not-permitted`. /// /// Note: This is similar to `symlinkat` in POSIX. + @since(version = 0.2.0) symlink-at: func( /// The contents of the symbolic link. old-path: string, @@ -568,6 +603,7 @@ interface types { /// /// Return `error-code::is-directory` if the path refers to a directory. /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + @since(version = 0.2.0) unlink-file-at: func( /// The relative path to a file to unlink. path: string, @@ -579,6 +615,7 @@ interface types { /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. /// wasi-filesystem does not expose device and inode numbers, so this function /// may be used instead. + @since(version = 0.2.0) is-same-object: func(other: borrow) -> bool; /// Return a hash of the metadata associated with a filesystem object referred @@ -590,7 +627,7 @@ interface types { /// replaced. It may also include a secret value chosen by the /// implementation and not otherwise exposed. /// - /// Implementations are encourated to provide the following properties: + /// Implementations are encouraged to provide the following properties: /// /// - If the file is not modified or replaced, the computed hash value should /// usually not change. @@ -600,12 +637,14 @@ interface types { /// computed hash. /// /// However, none of these is required. + @since(version = 0.2.0) metadata-hash: func() -> result; /// Return a hash of the metadata associated with a filesystem object referred /// to by a directory descriptor and a relative path. /// /// This performs the same hash computation as `metadata-hash`. + @since(version = 0.2.0) metadata-hash-at: func( /// Flags determining the method of how the path is resolved. path-flags: path-flags, @@ -615,8 +654,10 @@ interface types { } /// A stream of directory entries. + @since(version = 0.2.0) resource directory-entry-stream { /// Read a single directory entry from a `directory-entry-stream`. + @since(version = 0.2.0) read-directory-entry: func() -> result, error-code>; } @@ -630,5 +671,6 @@ interface types { /// /// Note that this function is fallible because not all stream-related /// errors are filesystem-related errors. + @since(version = 0.2.0) filesystem-error-code: func(err: borrow) -> option; } diff --git a/wit/deps/filesystem/world.wit b/wit/deps/filesystem/world.wit index 663f579..65597f9 100644 --- a/wit/deps/filesystem/world.wit +++ b/wit/deps/filesystem/world.wit @@ -1,6 +1,9 @@ -package wasi:filesystem@0.2.0; +package wasi:filesystem@0.2.6; +@since(version = 0.2.0) world imports { + @since(version = 0.2.0) import types; + @since(version = 0.2.0) import preopens; } diff --git a/wit/deps/http/proxy.wit b/wit/deps/http/proxy.wit index 687c24d..5bd9f99 100644 --- a/wit/deps/http/proxy.wit +++ b/wit/deps/http/proxy.wit @@ -1,32 +1,50 @@ -package wasi:http@0.2.0; +package wasi:http@0.2.6; -/// The `wasi:http/proxy` world captures a widely-implementable intersection of -/// hosts that includes HTTP forward and reverse proxies. Components targeting -/// this world may concurrently stream in and out any number of incoming and -/// outgoing HTTP requests. -world proxy { +/// The `wasi:http/imports` world imports all the APIs for HTTP proxies. +/// It is intended to be `include`d in other worlds. +@since(version = 0.2.0) +world imports { /// HTTP proxies have access to time and randomness. - include wasi:clocks/imports@0.2.0; - import wasi:random/random@0.2.0; + @since(version = 0.2.0) + import wasi:clocks/monotonic-clock@0.2.6; + @since(version = 0.2.0) + import wasi:clocks/wall-clock@0.2.6; + @since(version = 0.2.0) + import wasi:random/random@0.2.6; /// Proxies have standard output and error streams which are expected to /// terminate in a developer-facing console provided by the host. - import wasi:cli/stdout@0.2.0; - import wasi:cli/stderr@0.2.0; + @since(version = 0.2.0) + import wasi:cli/stdout@0.2.6; + @since(version = 0.2.0) + import wasi:cli/stderr@0.2.6; /// TODO: this is a temporary workaround until component tooling is able to /// gracefully handle the absence of stdin. Hosts must return an eof stream /// for this import, which is what wasi-libc + tooling will do automatically /// when this import is properly removed. - import wasi:cli/stdin@0.2.0; + @since(version = 0.2.0) + import wasi:cli/stdin@0.2.6; /// This is the default handler to use when user code simply wants to make an /// HTTP request (e.g., via `fetch()`). + @since(version = 0.2.0) import outgoing-handler; +} + +/// The `wasi:http/proxy` world captures a widely-implementable intersection of +/// hosts that includes HTTP forward and reverse proxies. Components targeting +/// this world may concurrently stream in and out any number of incoming and +/// outgoing HTTP requests. +@since(version = 0.2.0) +world proxy { + @since(version = 0.2.0) + include imports; /// The host delivers incoming HTTP requests to a component by calling the /// `handle` function of this exported interface. A host may arbitrarily reuse /// or not reuse component instance when delivering incoming HTTP requests and /// thus a component must be able to handle 0..N calls to `handle`. + @since(version = 0.2.0) export incoming-handler; } diff --git a/wit/deps/http/types.wit b/wit/deps/http/types.wit index 755ac6a..e174c3d 100644 --- a/wit/deps/http/types.wit +++ b/wit/deps/http/types.wit @@ -2,10 +2,10 @@ /// HTTP Requests and Responses, both incoming and outgoing, as well as /// their headers, trailers, and bodies. interface types { - use wasi:clocks/monotonic-clock@0.2.0.{duration}; - use wasi:io/streams@0.2.0.{input-stream, output-stream}; - use wasi:io/error@0.2.0.{error as io-error}; - use wasi:io/poll@0.2.0.{pollable}; + use wasi:clocks/monotonic-clock@0.2.6.{duration}; + use wasi:io/streams@0.2.6.{input-stream, output-stream}; + use wasi:io/error@0.2.6.{error as io-error}; + use wasi:io/poll@0.2.6.{pollable}; /// This type corresponds to HTTP standard Methods. variant method { diff --git a/wit/deps/io/error.wit b/wit/deps/io/error.wit index 22e5b64..784f74a 100644 --- a/wit/deps/io/error.wit +++ b/wit/deps/io/error.wit @@ -1,6 +1,6 @@ -package wasi:io@0.2.0; - +package wasi:io@0.2.6; +@since(version = 0.2.0) interface error { /// A resource which represents some error information. /// @@ -11,16 +11,15 @@ interface error { /// `wasi:io/streams/stream-error` type. /// /// To provide more specific error information, other interfaces may - /// provide functions to further "downcast" this error into more specific - /// error information. For example, `error`s returned in streams derived - /// from filesystem types to be described using the filesystem's own - /// error-code type, using the function - /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter - /// `borrow` and returns - /// `option`. + /// offer functions to "downcast" this error into more specific types. For example, + /// errors returned from streams derived from filesystem types can be described using + /// the filesystem's own error-code type. This is done using the function + /// `wasi:filesystem/types/filesystem-error-code`, which takes a `borrow` + /// parameter and returns an `option`. /// /// The set of functions which can "downcast" an `error` into a more /// concrete type is open. + @since(version = 0.2.0) resource error { /// Returns a string that is suitable to assist humans in debugging /// this error. @@ -29,6 +28,7 @@ interface error { /// It may change across platforms, hosts, or other implementation /// details. Parsing this string is a major platform-compatibility /// hazard. + @since(version = 0.2.0) to-debug-string: func() -> string; } } diff --git a/wit/deps/io/poll.wit b/wit/deps/io/poll.wit index ddc67f8..7f71183 100644 --- a/wit/deps/io/poll.wit +++ b/wit/deps/io/poll.wit @@ -1,22 +1,26 @@ -package wasi:io@0.2.0; +package wasi:io@0.2.6; /// A poll API intended to let users wait for I/O events on multiple handles /// at once. +@since(version = 0.2.0) interface poll { /// `pollable` represents a single I/O event which may be ready, or not. + @since(version = 0.2.0) resource pollable { - /// Return the readiness of a pollable. This function never blocks. - /// - /// Returns `true` when the pollable is ready, and `false` otherwise. - ready: func() -> bool; + /// Return the readiness of a pollable. This function never blocks. + /// + /// Returns `true` when the pollable is ready, and `false` otherwise. + @since(version = 0.2.0) + ready: func() -> bool; - /// `block` returns immediately if the pollable is ready, and otherwise - /// blocks until ready. - /// - /// This function is equivalent to calling `poll.poll` on a list - /// containing only this pollable. - block: func(); + /// `block` returns immediately if the pollable is ready, and otherwise + /// blocks until ready. + /// + /// This function is equivalent to calling `poll.poll` on a list + /// containing only this pollable. + @since(version = 0.2.0) + block: func(); } /// Poll for completion on a set of pollables. @@ -27,8 +31,9 @@ interface poll { /// The result `list` contains one or more indices of handles in the /// argument list that is ready for I/O. /// - /// If the list contains more elements than can be indexed with a `u32` - /// value, this function traps. + /// This function traps if either: + /// - the list is empty, or: + /// - the list contains more elements than can be indexed with a `u32` value. /// /// A timeout can be implemented by adding a pollable from the /// wasi-clocks API to the list. @@ -36,6 +41,7 @@ interface poll { /// This function does not return a `result`; polling in itself does not /// do any I/O so it doesn't fail. If any of the I/O sources identified by /// the pollables has an error, it is indicated by marking the source as - /// being reaedy for I/O. + /// being ready for I/O. + @since(version = 0.2.0) poll: func(in: list>) -> list; } diff --git a/wit/deps/io/streams.wit b/wit/deps/io/streams.wit index 6d2f871..c5da38c 100644 --- a/wit/deps/io/streams.wit +++ b/wit/deps/io/streams.wit @@ -1,19 +1,26 @@ -package wasi:io@0.2.0; +package wasi:io@0.2.6; /// WASI I/O is an I/O abstraction API which is currently focused on providing /// stream types. /// /// In the future, the component model is expected to add built-in stream types; /// when it does, they are expected to subsume this API. +@since(version = 0.2.0) interface streams { + @since(version = 0.2.0) use error.{error}; + @since(version = 0.2.0) use poll.{pollable}; /// An error for input-stream and output-stream operations. + @since(version = 0.2.0) variant stream-error { /// The last operation (a write or flush) failed before completion. /// /// More information is available in the `error` payload. + /// + /// After this, the stream will be closed. All future operations return + /// `stream-error::closed`. last-operation-failed(error), /// The stream is closed: no more input will be accepted by the /// stream. A closed output-stream will return this error on all @@ -29,6 +36,7 @@ interface streams { /// available, which could even be zero. To wait for data to be available, /// use the `subscribe` function to obtain a `pollable` which can be polled /// for using `wasi:io/poll`. + @since(version = 0.2.0) resource input-stream { /// Perform a non-blocking read from the stream. /// @@ -56,6 +64,7 @@ interface streams { /// is not possible to allocate in wasm32, or not desirable to allocate as /// as a return value by the callee. The callee may return a list of bytes /// less than `len` in size while more bytes are available for reading. + @since(version = 0.2.0) read: func( /// The maximum number of bytes to read len: u64 @@ -63,6 +72,7 @@ interface streams { /// Read bytes from a stream, after blocking until at least one byte can /// be read. Except for blocking, behavior is identical to `read`. + @since(version = 0.2.0) blocking-read: func( /// The maximum number of bytes to read len: u64 @@ -72,6 +82,7 @@ interface streams { /// /// Behaves identical to `read`, except instead of returning a list /// of bytes, returns the number of bytes consumed from the stream. + @since(version = 0.2.0) skip: func( /// The maximum number of bytes to skip. len: u64, @@ -79,6 +90,7 @@ interface streams { /// Skip bytes from a stream, after blocking until at least one byte /// can be skipped. Except for blocking behavior, identical to `skip`. + @since(version = 0.2.0) blocking-skip: func( /// The maximum number of bytes to skip. len: u64, @@ -90,6 +102,7 @@ interface streams { /// The created `pollable` is a child resource of the `input-stream`. /// Implementations may trap if the `input-stream` is dropped before /// all derived `pollable`s created with this function are dropped. + @since(version = 0.2.0) subscribe: func() -> pollable; } @@ -102,6 +115,11 @@ interface streams { /// promptly, which could even be zero. To wait for the stream to be ready to /// accept data, the `subscribe` function to obtain a `pollable` which can be /// polled for using `wasi:io/poll`. + /// + /// Dropping an `output-stream` while there's still an active write in + /// progress may result in the data being lost. Before dropping the stream, + /// be sure to fully flush your writes. + @since(version = 0.2.0) resource output-stream { /// Check readiness for writing. This function never blocks. /// @@ -112,6 +130,7 @@ interface streams { /// When this function returns 0 bytes, the `subscribe` pollable will /// become ready when this function will report at least 1 byte, or an /// error. + @since(version = 0.2.0) check-write: func() -> result; /// Perform a write. This function never blocks. @@ -127,6 +146,7 @@ interface streams { /// /// returns Err(closed) without writing if the stream has closed since /// the last call to check-write provided a permit. + @since(version = 0.2.0) write: func( contents: list ) -> result<_, stream-error>; @@ -155,6 +175,7 @@ interface streams { /// // Check for any errors that arose during `flush` /// let _ = this.check-write(); // eliding error handling /// ``` + @since(version = 0.2.0) blocking-write-and-flush: func( contents: list ) -> result<_, stream-error>; @@ -169,14 +190,16 @@ interface streams { /// writes (`check-write` will return `ok(0)`) until the flush has /// completed. The `subscribe` pollable will become ready when the /// flush has completed and the stream can accept more writes. + @since(version = 0.2.0) flush: func() -> result<_, stream-error>; /// Request to flush buffered output, and block until flush completes /// and stream is ready for writing again. + @since(version = 0.2.0) blocking-flush: func() -> result<_, stream-error>; /// Create a `pollable` which will resolve once the output-stream - /// is ready for more writing, or an error has occured. When this + /// is ready for more writing, or an error has occurred. When this /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an /// error. /// @@ -185,6 +208,7 @@ interface streams { /// The created `pollable` is a child resource of the `output-stream`. /// Implementations may trap if the `output-stream` is dropped before /// all derived `pollable`s created with this function are dropped. + @since(version = 0.2.0) subscribe: func() -> pollable; /// Write zeroes to a stream. @@ -193,6 +217,7 @@ interface streams { /// preconditions (must use check-write first), but instead of /// passing a list of bytes, you simply pass the number of zero-bytes /// that should be written. + @since(version = 0.2.0) write-zeroes: func( /// The number of zero-bytes to write len: u64 @@ -222,6 +247,7 @@ interface streams { /// // Check for any errors that arose during `flush` /// let _ = this.check-write(); // eliding error handling /// ``` + @since(version = 0.2.0) blocking-write-zeroes-and-flush: func( /// The number of zero-bytes to write len: u64 @@ -229,7 +255,7 @@ interface streams { /// Read from one stream and write to another. /// - /// The behavior of splice is equivelant to: + /// The behavior of splice is equivalent to: /// 1. calling `check-write` on the `output-stream` /// 2. calling `read` on the `input-stream` with the smaller of the /// `check-write` permitted length and the `len` provided to `splice` @@ -240,6 +266,7 @@ interface streams { /// /// This function returns the number of bytes transferred; it may be less /// than `len`. + @since(version = 0.2.0) splice: func( /// The stream to read from src: borrow, @@ -252,6 +279,7 @@ interface streams { /// This is similar to `splice`, except that it blocks until the /// `output-stream` is ready for writing, and the `input-stream` /// is ready for reading, before performing the `splice`. + @since(version = 0.2.0) blocking-splice: func( /// The stream to read from src: borrow, diff --git a/wit/deps/io/world.wit b/wit/deps/io/world.wit index 5f0b43f..84c85c0 100644 --- a/wit/deps/io/world.wit +++ b/wit/deps/io/world.wit @@ -1,6 +1,10 @@ -package wasi:io@0.2.0; +package wasi:io@0.2.6; +@since(version = 0.2.0) world imports { + @since(version = 0.2.0) import streams; + + @since(version = 0.2.0) import poll; } diff --git a/wit/deps/random/insecure-seed.wit b/wit/deps/random/insecure-seed.wit index 47210ac..d3dc03a 100644 --- a/wit/deps/random/insecure-seed.wit +++ b/wit/deps/random/insecure-seed.wit @@ -1,8 +1,9 @@ -package wasi:random@0.2.0; +package wasi:random@0.2.6; /// The insecure-seed interface for seeding hash-map DoS resistance. /// /// It is intended to be portable at least between Unix-family platforms and /// Windows. +@since(version = 0.2.0) interface insecure-seed { /// Return a 128-bit value that may contain a pseudo-random value. /// @@ -21,5 +22,6 @@ interface insecure-seed { /// This will likely be changed to a value import, to prevent it from being /// called multiple times and potentially used for purposes other than DoS /// protection. + @since(version = 0.2.0) insecure-seed: func() -> tuple; } diff --git a/wit/deps/random/insecure.wit b/wit/deps/random/insecure.wit index c58f4ee..d4d0284 100644 --- a/wit/deps/random/insecure.wit +++ b/wit/deps/random/insecure.wit @@ -1,8 +1,9 @@ -package wasi:random@0.2.0; +package wasi:random@0.2.6; /// The insecure interface for insecure pseudo-random numbers. /// /// It is intended to be portable at least between Unix-family platforms and /// Windows. +@since(version = 0.2.0) interface insecure { /// Return `len` insecure pseudo-random bytes. /// @@ -12,11 +13,13 @@ interface insecure { /// There are no requirements on the values of the returned bytes, however /// implementations are encouraged to return evenly distributed values with /// a long period. + @since(version = 0.2.0) get-insecure-random-bytes: func(len: u64) -> list; /// Return an insecure pseudo-random `u64` value. /// /// This function returns the same type of pseudo-random data as /// `get-insecure-random-bytes`, represented as a `u64`. + @since(version = 0.2.0) get-insecure-random-u64: func() -> u64; } diff --git a/wit/deps/random/random.wit b/wit/deps/random/random.wit index 0c017f0..a0ff956 100644 --- a/wit/deps/random/random.wit +++ b/wit/deps/random/random.wit @@ -1,8 +1,9 @@ -package wasi:random@0.2.0; +package wasi:random@0.2.6; /// WASI Random is a random data API. /// /// It is intended to be portable at least between Unix-family platforms and /// Windows. +@since(version = 0.2.0) interface random { /// Return `len` cryptographically-secure random or pseudo-random bytes. /// @@ -16,11 +17,13 @@ interface random { /// This function must always return fresh data. Deterministic environments /// must omit this function, rather than implementing it with deterministic /// data. + @since(version = 0.2.0) get-random-bytes: func(len: u64) -> list; /// Return a cryptographically-secure random or pseudo-random `u64` value. /// /// This function returns the same type of data as `get-random-bytes`, /// represented as a `u64`. + @since(version = 0.2.0) get-random-u64: func() -> u64; } diff --git a/wit/deps/random/world.wit b/wit/deps/random/world.wit index 3da3491..099f47b 100644 --- a/wit/deps/random/world.wit +++ b/wit/deps/random/world.wit @@ -1,7 +1,13 @@ -package wasi:random@0.2.0; +package wasi:random@0.2.6; +@since(version = 0.2.0) world imports { + @since(version = 0.2.0) import random; + + @since(version = 0.2.0) import insecure; + + @since(version = 0.2.0) import insecure-seed; } diff --git a/wit/deps/sockets/instance-network.wit b/wit/deps/sockets/instance-network.wit index e455d0f..5f6e6c1 100644 --- a/wit/deps/sockets/instance-network.wit +++ b/wit/deps/sockets/instance-network.wit @@ -1,9 +1,11 @@ /// This interface provides a value-export of the default network handle.. +@since(version = 0.2.0) interface instance-network { + @since(version = 0.2.0) use network.{network}; /// Get a handle to the default network. + @since(version = 0.2.0) instance-network: func() -> network; - } diff --git a/wit/deps/sockets/ip-name-lookup.wit b/wit/deps/sockets/ip-name-lookup.wit index 8e639ec..ee6419e 100644 --- a/wit/deps/sockets/ip-name-lookup.wit +++ b/wit/deps/sockets/ip-name-lookup.wit @@ -1,9 +1,10 @@ - +@since(version = 0.2.0) interface ip-name-lookup { - use wasi:io/poll@0.2.0.{pollable}; + @since(version = 0.2.0) + use wasi:io/poll@0.2.6.{pollable}; + @since(version = 0.2.0) use network.{network, error-code, ip-address}; - /// Resolve an internet host name to a list of IP addresses. /// /// Unicode domain names are automatically converted to ASCII using IDNA encoding. @@ -24,8 +25,10 @@ interface ip-name-lookup { /// - /// - /// - + @since(version = 0.2.0) resolve-addresses: func(network: borrow, name: string) -> result; + @since(version = 0.2.0) resource resolve-address-stream { /// Returns the next address from the resolver. /// @@ -40,12 +43,14 @@ interface ip-name-lookup { /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) + @since(version = 0.2.0) resolve-next-address: func() -> result, error-code>; /// Create a `pollable` which will resolve once the stream is ready for I/O. /// - /// Note: this function is here for WASI Preview2 only. + /// Note: this function is here for WASI 0.2 only. /// It's planned to be removed when `future` is natively supported in Preview3. + @since(version = 0.2.0) subscribe: func() -> pollable; } } diff --git a/wit/deps/sockets/network.wit b/wit/deps/sockets/network.wit index 9cadf06..6ca98b6 100644 --- a/wit/deps/sockets/network.wit +++ b/wit/deps/sockets/network.wit @@ -1,8 +1,12 @@ - +@since(version = 0.2.0) interface network { + @unstable(feature = network-error-code) + use wasi:io/error@0.2.6.{error}; + /// An opaque resource that represents access to (a subset of) the network. /// This enables context-based security for networking. /// There is no need for this to map 1:1 to a physical network interface. + @since(version = 0.2.0) resource network; /// Error codes. @@ -17,6 +21,7 @@ interface network { /// - `concurrency-conflict` /// /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. + @since(version = 0.2.0) enum error-code { /// Unknown error unknown, @@ -103,6 +108,20 @@ interface network { permanent-resolver-failure, } + /// Attempts to extract a network-related `error-code` from the stream + /// `error` provided. + /// + /// Stream operations which return `stream-error::last-operation-failed` + /// have a payload with more information about the operation that failed. + /// This payload can be passed through to this function to see if there's + /// network-related information about the error to return. + /// + /// Note that this function is fallible because not all stream-related + /// errors are network-related errors. + @unstable(feature = network-error-code) + network-error-code: func(err: borrow) -> option; + + @since(version = 0.2.0) enum ip-address-family { /// Similar to `AF_INET` in POSIX. ipv4, @@ -111,14 +130,18 @@ interface network { ipv6, } + @since(version = 0.2.0) type ipv4-address = tuple; + @since(version = 0.2.0) type ipv6-address = tuple; + @since(version = 0.2.0) variant ip-address { ipv4(ipv4-address), ipv6(ipv6-address), } + @since(version = 0.2.0) record ipv4-socket-address { /// sin_port port: u16, @@ -126,6 +149,7 @@ interface network { address: ipv4-address, } + @since(version = 0.2.0) record ipv6-socket-address { /// sin6_port port: u16, @@ -137,9 +161,9 @@ interface network { scope-id: u32, } + @since(version = 0.2.0) variant ip-socket-address { ipv4(ipv4-socket-address), ipv6(ipv6-socket-address), } - } diff --git a/wit/deps/sockets/tcp-create-socket.wit b/wit/deps/sockets/tcp-create-socket.wit index c7ddf1f..eedbd30 100644 --- a/wit/deps/sockets/tcp-create-socket.wit +++ b/wit/deps/sockets/tcp-create-socket.wit @@ -1,6 +1,8 @@ - +@since(version = 0.2.0) interface tcp-create-socket { + @since(version = 0.2.0) use network.{network, error-code, ip-address-family}; + @since(version = 0.2.0) use tcp.{tcp-socket}; /// Create a new TCP socket. @@ -23,5 +25,6 @@ interface tcp-create-socket { /// - /// - /// - + @since(version = 0.2.0) create-tcp-socket: func(address-family: ip-address-family) -> result; } diff --git a/wit/deps/sockets/tcp.wit b/wit/deps/sockets/tcp.wit index 5902b9e..beefd7b 100644 --- a/wit/deps/sockets/tcp.wit +++ b/wit/deps/sockets/tcp.wit @@ -1,10 +1,15 @@ - +@since(version = 0.2.0) interface tcp { - use wasi:io/streams@0.2.0.{input-stream, output-stream}; - use wasi:io/poll@0.2.0.{pollable}; - use wasi:clocks/monotonic-clock@0.2.0.{duration}; + @since(version = 0.2.0) + use wasi:io/streams@0.2.6.{input-stream, output-stream}; + @since(version = 0.2.0) + use wasi:io/poll@0.2.6.{pollable}; + @since(version = 0.2.0) + use wasi:clocks/monotonic-clock@0.2.6.{duration}; + @since(version = 0.2.0) use network.{network, error-code, ip-socket-address, ip-address-family}; + @since(version = 0.2.0) enum shutdown-type { /// Similar to `SHUT_RD` in POSIX. receive, @@ -27,8 +32,8 @@ interface tcp { /// - `connect-in-progress` /// - `connected` /// - `closed` - /// See - /// for a more information. + /// See + /// for more information. /// /// Note: Except where explicitly mentioned, whenever this documentation uses /// the term "bound" without backticks it actually means: in the `bound` state *or higher*. @@ -37,6 +42,7 @@ interface tcp { /// In addition to the general error codes documented on the /// `network::error-code` type, TCP socket methods may always return /// `error(invalid-state)` when in the `closed` state. + @since(version = 0.2.0) resource tcp-socket { /// Bind the socket to a specific network on the provided IP address and port. /// @@ -76,13 +82,15 @@ interface tcp { /// - /// - /// - + @since(version = 0.2.0) start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + @since(version = 0.2.0) finish-bind: func() -> result<_, error-code>; /// Connect to a remote endpoint. /// /// On success: - /// - the socket is transitioned into the `connection` state. + /// - the socket is transitioned into the `connected` state. /// - a pair of streams is returned that can be used to read & write to the connection /// /// After a failed connection attempt, the socket will be in the `closed` @@ -121,7 +129,9 @@ interface tcp { /// - /// - /// - + @since(version = 0.2.0) start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; + @since(version = 0.2.0) finish-connect: func() -> result, error-code>; /// Start listening for new connections. @@ -149,7 +159,9 @@ interface tcp { /// - /// - /// - + @since(version = 0.2.0) start-listen: func() -> result<_, error-code>; + @since(version = 0.2.0) finish-listen: func() -> result<_, error-code>; /// Accept a new client socket. @@ -178,6 +190,7 @@ interface tcp { /// - /// - /// - + @since(version = 0.2.0) accept: func() -> result, error-code>; /// Get the bound local address. @@ -196,6 +209,7 @@ interface tcp { /// - /// - /// - + @since(version = 0.2.0) local-address: func() -> result; /// Get the remote address. @@ -208,16 +222,19 @@ interface tcp { /// - /// - /// - + @since(version = 0.2.0) remote-address: func() -> result; /// Whether the socket is in the `listening` state. /// /// Equivalent to the SO_ACCEPTCONN socket option. + @since(version = 0.2.0) is-listening: func() -> bool; /// Whether this is a IPv4 or IPv6 socket. /// /// Equivalent to the SO_DOMAIN socket option. + @since(version = 0.2.0) address-family: func() -> ip-address-family; /// Hints the desired listen queue size. Implementations are free to ignore this. @@ -229,6 +246,7 @@ interface tcp { /// - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen. /// - `invalid-argument`: (set) The provided value was 0. /// - `invalid-state`: (set) The socket is in the `connect-in-progress` or `connected` state. + @since(version = 0.2.0) set-listen-backlog-size: func(value: u64) -> result<_, error-code>; /// Enables or disables keepalive. @@ -240,7 +258,9 @@ interface tcp { /// These properties can be configured while `keep-alive-enabled` is false, but only come into effect when `keep-alive-enabled` is true. /// /// Equivalent to the SO_KEEPALIVE socket option. + @since(version = 0.2.0) keep-alive-enabled: func() -> result; + @since(version = 0.2.0) set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; /// Amount of time the connection has to be idle before TCP starts sending keepalive packets. @@ -253,7 +273,9 @@ interface tcp { /// /// # Typical errors /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.2.0) keep-alive-idle-time: func() -> result; + @since(version = 0.2.0) set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; /// The time between keepalive packets. @@ -266,7 +288,9 @@ interface tcp { /// /// # Typical errors /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.2.0) keep-alive-interval: func() -> result; + @since(version = 0.2.0) set-keep-alive-interval: func(value: duration) -> result<_, error-code>; /// The maximum amount of keepalive packets TCP should send before aborting the connection. @@ -279,7 +303,9 @@ interface tcp { /// /// # Typical errors /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.2.0) keep-alive-count: func() -> result; + @since(version = 0.2.0) set-keep-alive-count: func(value: u32) -> result<_, error-code>; /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. @@ -288,7 +314,9 @@ interface tcp { /// /// # Typical errors /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + @since(version = 0.2.0) hop-limit: func() -> result; + @since(version = 0.2.0) set-hop-limit: func(value: u8) -> result<_, error-code>; /// The kernel buffer space reserved for sends/receives on this socket. @@ -301,9 +329,13 @@ interface tcp { /// /// # Typical errors /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.2.0) receive-buffer-size: func() -> result; + @since(version = 0.2.0) set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + @since(version = 0.2.0) send-buffer-size: func() -> result; + @since(version = 0.2.0) set-send-buffer-size: func(value: u64) -> result<_, error-code>; /// Create a `pollable` which can be used to poll for, or block on, @@ -318,11 +350,12 @@ interface tcp { /// `subscribe` only has to be called once per socket and can then be /// (re)used for the remainder of the socket's lifetime. /// - /// See - /// for a more information. + /// See + /// for more information. /// - /// Note: this function is here for WASI Preview2 only. + /// Note: this function is here for WASI 0.2 only. /// It's planned to be removed when `future` is natively supported in Preview3. + @since(version = 0.2.0) subscribe: func() -> pollable; /// Initiate a graceful shutdown. @@ -335,7 +368,7 @@ interface tcp { /// associated with this socket will be closed and a FIN packet will be sent. /// - `both`: Same effect as `receive` & `send` combined. /// - /// This function is idempotent. Shutting a down a direction more than once + /// This function is idempotent; shutting down a direction more than once /// has no effect and returns `ok`. /// /// The shutdown function does not close (drop) the socket. @@ -348,6 +381,7 @@ interface tcp { /// - /// - /// - + @since(version = 0.2.0) shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; } } diff --git a/wit/deps/sockets/udp-create-socket.wit b/wit/deps/sockets/udp-create-socket.wit index 0482d1f..e8eeacb 100644 --- a/wit/deps/sockets/udp-create-socket.wit +++ b/wit/deps/sockets/udp-create-socket.wit @@ -1,6 +1,8 @@ - +@since(version = 0.2.0) interface udp-create-socket { + @since(version = 0.2.0) use network.{network, error-code, ip-address-family}; + @since(version = 0.2.0) use udp.{udp-socket}; /// Create a new UDP socket. @@ -23,5 +25,6 @@ interface udp-create-socket { /// - /// - /// - + @since(version = 0.2.0) create-udp-socket: func(address-family: ip-address-family) -> result; } diff --git a/wit/deps/sockets/udp.wit b/wit/deps/sockets/udp.wit index d987a0a..9dbe693 100644 --- a/wit/deps/sockets/udp.wit +++ b/wit/deps/sockets/udp.wit @@ -1,9 +1,12 @@ - +@since(version = 0.2.0) interface udp { - use wasi:io/poll@0.2.0.{pollable}; + @since(version = 0.2.0) + use wasi:io/poll@0.2.6.{pollable}; + @since(version = 0.2.0) use network.{network, error-code, ip-socket-address, ip-address-family}; /// A received datagram. + @since(version = 0.2.0) record incoming-datagram { /// The payload. /// @@ -19,6 +22,7 @@ interface udp { } /// A datagram to be sent out. + @since(version = 0.2.0) record outgoing-datagram { /// The payload. data: list, @@ -33,9 +37,8 @@ interface udp { remote-address: option, } - - /// A UDP socket handle. + @since(version = 0.2.0) resource udp-socket { /// Bind the socket to a specific network on the provided IP address and port. /// @@ -63,7 +66,9 @@ interface udp { /// - /// - /// - + @since(version = 0.2.0) start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + @since(version = 0.2.0) finish-bind: func() -> result<_, error-code>; /// Set up inbound & outbound communication channels, optionally to a specific peer. @@ -106,6 +111,7 @@ interface udp { /// - /// - /// - + @since(version = 0.2.0) %stream: func(remote-address: option) -> result, error-code>; /// Get the current bound address. @@ -124,6 +130,7 @@ interface udp { /// - /// - /// - + @since(version = 0.2.0) local-address: func() -> result; /// Get the address the socket is currently streaming to. @@ -136,11 +143,13 @@ interface udp { /// - /// - /// - + @since(version = 0.2.0) remote-address: func() -> result; /// Whether this is a IPv4 or IPv6 socket. /// /// Equivalent to the SO_DOMAIN socket option. + @since(version = 0.2.0) address-family: func() -> ip-address-family; /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. @@ -149,7 +158,9 @@ interface udp { /// /// # Typical errors /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + @since(version = 0.2.0) unicast-hop-limit: func() -> result; + @since(version = 0.2.0) set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; /// The kernel buffer space reserved for sends/receives on this socket. @@ -162,18 +173,24 @@ interface udp { /// /// # Typical errors /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.2.0) receive-buffer-size: func() -> result; + @since(version = 0.2.0) set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + @since(version = 0.2.0) send-buffer-size: func() -> result; + @since(version = 0.2.0) set-send-buffer-size: func(value: u64) -> result<_, error-code>; /// Create a `pollable` which will resolve once the socket is ready for I/O. /// - /// Note: this function is here for WASI Preview2 only. + /// Note: this function is here for WASI 0.2 only. /// It's planned to be removed when `future` is natively supported in Preview3. + @since(version = 0.2.0) subscribe: func() -> pollable; } + @since(version = 0.2.0) resource incoming-datagram-stream { /// Receive messages on the socket. /// @@ -198,15 +215,18 @@ interface udp { /// - /// - /// - + @since(version = 0.2.0) receive: func(max-results: u64) -> result, error-code>; /// Create a `pollable` which will resolve once the stream is ready to receive again. /// - /// Note: this function is here for WASI Preview2 only. + /// Note: this function is here for WASI 0.2 only. /// It's planned to be removed when `future` is natively supported in Preview3. + @since(version = 0.2.0) subscribe: func() -> pollable; } + @since(version = 0.2.0) resource outgoing-datagram-stream { /// Check readiness for sending. This function never blocks. /// @@ -255,12 +275,14 @@ interface udp { /// - /// - /// - + @since(version = 0.2.0) send: func(datagrams: list) -> result; /// Create a `pollable` which will resolve once the stream is ready to send again. /// - /// Note: this function is here for WASI Preview2 only. + /// Note: this function is here for WASI 0.2 only. /// It's planned to be removed when `future` is natively supported in Preview3. + @since(version = 0.2.0) subscribe: func() -> pollable; } } diff --git a/wit/deps/sockets/world.wit b/wit/deps/sockets/world.wit index f8bb92a..e86f02c 100644 --- a/wit/deps/sockets/world.wit +++ b/wit/deps/sockets/world.wit @@ -1,11 +1,19 @@ -package wasi:sockets@0.2.0; +package wasi:sockets@0.2.6; +@since(version = 0.2.0) world imports { + @since(version = 0.2.0) import instance-network; + @since(version = 0.2.0) import network; + @since(version = 0.2.0) import udp; + @since(version = 0.2.0) import udp-create-socket; + @since(version = 0.2.0) import tcp; + @since(version = 0.2.0) import tcp-create-socket; + @since(version = 0.2.0) import ip-name-lookup; } diff --git a/wit/viceroy.wit b/wit/viceroy.wit index 646dd32..7c68bd5 100644 --- a/wit/viceroy.wit +++ b/wit/viceroy.wit @@ -1,5 +1,7 @@ package fastly:viceroy; -world compute { - include fastly:api/compute; -} \ No newline at end of file +world viceroy { + include fastly:compute/custom-imports; + include fastly:adapter/adapter-imports; + include fastly:compute/custom-exports; +}