diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..a56e645 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,22 @@ +{ + "name": "Lambda Rust HTTP", + "image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye", + "features": { + "ghcr.io/devcontainers/features/aws-cli:1": {}, + "ghcr.io/devcontainers/features/terraform:1": {}, + "ghcr.io/jajera/features/zip:1": {}, + "ghcr.io/devcontainers-extra/features/zig:1": {} + }, + "customizations": { + "vscode": { + "extensions": [ + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + "ms-vscode.vscode-json", + "hashicorp.terraform" + ] + } + }, + "postCreateCommand": "cargo install cargo-lambda", + "remoteUser": "vscode" +} diff --git a/.github/workflows/commitmsg-conform.yml b/.github/workflows/commitmsg-conform.yml new file mode 100644 index 0000000..84b9661 --- /dev/null +++ b/.github/workflows/commitmsg-conform.yml @@ -0,0 +1,11 @@ +name: commit-message-conformance +on: + pull_request: {} +permissions: + statuses: write + checks: write + contents: read + pull-requests: read +jobs: + commitmsg-conform: + uses: actionsforge/actions/.github/workflows/commitmsg-conform.yml@main \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..78858a0 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,177 @@ +name: Build and Deploy Rust Lambda + +on: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +# Cancel in-progress jobs when new commits are pushed +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + FUNCTION_NAME: lambda-rust-http + RUNTIME: provided.al2023 + ARCHITECTURE: arm64 + +permissions: + contents: write + packages: write + +jobs: + validate: + name: Validate and Test + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: aarch64-unknown-linux-gnu + override: true + + - name: Cache Zig + uses: actions/cache@v4 + with: + path: | + ~/.zig + zig-linux-x86_64-0.13.0 + key: ${{ runner.os }}-zig-0.13.0 + + - name: Install Zig + run: | + if [ ! -d "zig-linux-x86_64-0.13.0" ]; then + curl -L https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | tar -xJ + fi + echo "$PWD/zig-linux-x86_64-0.13.0" >> $GITHUB_PATH + + - name: Cache Rust dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + ~/.cargo/bin + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Install cargo-lambda + run: | + cargo install cargo-lambda + + - name: Run clippy + run: | + cargo clippy --all-targets --all-features -- -D warnings + + - name: Run tests + run: | + cargo test + + - name: Build Lambda function + run: | + cargo lambda build --release --arm64 + + - name: Verify binary + run: | + if [ ! -f "target/lambda/bootstrap/bootstrap" ]; then + echo "โŒ Binary not found at target/lambda/bootstrap/bootstrap" + exit 1 + fi + echo "โœ… Binary created successfully" + ls -la target/lambda/bootstrap/bootstrap + file target/lambda/bootstrap/bootstrap + + release: + name: Create Release + runs-on: ubuntu-latest + needs: validate + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: aarch64-unknown-linux-gnu + override: true + + - name: Cache Zig + uses: actions/cache@v4 + with: + path: | + ~/.zig + zig-linux-x86_64-0.13.0 + key: ${{ runner.os }}-zig-0.13.0 + + - name: Install Zig + run: | + if [ ! -d "zig-linux-x86_64-0.13.0" ]; then + curl -L https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | tar -xJ + fi + echo "$PWD/zig-linux-x86_64-0.13.0" >> $GITHUB_PATH + + - name: Cache Rust dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + ~/.cargo/bin + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Install cargo-lambda + run: | + cargo install cargo-lambda + + - name: Build Lambda function + run: | + cargo lambda build --release --arm64 + + - name: Create deployment package + run: | + cd target/lambda/bootstrap + zip -r lambda-rust-http.zip bootstrap + cd ../../.. + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: target/lambda/bootstrap/lambda-rust-http.zip + tag_name: v${{ github.run_number }} + name: Release v${{ github.run_number }} + body: | + ## Rust Lambda Function Release + + **Function:** ${{ env.FUNCTION_NAME }} + **Runtime:** ${{ env.RUNTIME }} + **Architecture:** ${{ env.ARCHITECTURE }} + + ### Installation + 1. Download the `lambda-rust-http.zip` file + 2. Upload to AWS Lambda console or use AWS CLI + 3. Set handler to: `bootstrap` + 4. Set runtime to: `provided.al2023` + 5. Set architecture to: `arm64` + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Tag Latest + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -f latest + git push origin latest --force \ No newline at end of file diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml new file mode 100644 index 0000000..6ed56d5 --- /dev/null +++ b/.github/workflows/markdown-lint.yml @@ -0,0 +1,11 @@ +name: markdown-lint +on: + pull_request: {} +permissions: + statuses: write + checks: write + contents: read + pull-requests: read +jobs: + markdown-lint: + uses: actionsforge/actions/.github/workflows/markdown-lint.yml@main \ No newline at end of file diff --git a/.gitignore b/.gitignore index ad67955..a848d49 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,38 @@ -# Generated by Cargo -# will have compiled files and executables -debug -target - -# These are backup files generated by rustfmt +# Rust +/target/ **/*.rs.bk +Cargo.lock + +# Lambda build artifacts +target/lambda/ +*.zip +bootstrap +target/lambda/bootstrap/ +target/lambda/lambda-rust-http/ + +# Terraform +*.tfstate +*.tfstate.* +.terraform/ +.terraform.lock.hcl + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb +# Logs +*.log -# Generated by cargo mutants -# Contains mutation testing data -**/mutants.out*/ +# Environment variables +.env +.env.local +.env.*.local -# RustRover -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +# AWS +.aws/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4a6ac46 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "lambda-rust-http" +version = "0.1.0" +edition = "2021" + +[dependencies] +lambda_http = "0.16" +lambda_runtime = "0.14" +tokio = { version = "1.47", features = ["full"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tracing = "0.1" +tracing-subscriber = "0.3" + +[dev-dependencies] +cargo-lambda = "1.8.6" + +[[bin]] +name = "bootstrap" +path = "src/main.rs" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3680b2b --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +.PHONY: help build clean watch deploy destroy test + +help: ## Show this help message + @echo "Available commands:" + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +build: ## Build the Lambda function for production + @echo "๐Ÿ”จ Building Lambda function..." + cargo lambda build --release --arm64 + @echo "โœ… Build complete!" + +clean: ## Clean build artifacts + @echo "๐Ÿงน Cleaning build artifacts..." + cargo clean + @echo "โœ… Clean complete!" + +watch: ## Watch for changes and run locally + @echo "๐Ÿ‘€ Starting cargo lambda watch..." + cargo lambda watch + +deploy: build ## Build and deploy to AWS + @echo "๐Ÿš€ Deploying to AWS..." + cd infra && terraform init && terraform apply -auto-approve + @echo "โœ… Deployment complete!" + +destroy: ## Destroy AWS infrastructure + @echo "๐Ÿ—‘๏ธ Destroying AWS infrastructure..." + cd infra && terraform destroy -auto-approve + @echo "โœ… Destruction complete!" + +test: ## Test the deployed function + @echo "๐Ÿงช Testing deployed function..." + @cd infra && terraform output -raw api_gateway_url | xargs -I {} curl -s {} | jq . + +plan: ## Plan Terraform changes + @echo "๐Ÿ“‹ Planning Terraform changes..." + cd infra && terraform plan + +fmt: ## Format Rust code + @echo "๐ŸŽจ Formatting Rust code..." + cargo fmt + +check: ## Check Rust code + @echo "๐Ÿ” Checking Rust code..." + cargo check + +clippy: ## Run Clippy linter + @echo "๐Ÿ” Running Clippy..." + cargo clippy + +install-deps: ## Install development dependencies + @echo "๐Ÿ“ฆ Installing development dependencies..." + cargo install cargo-lambda + @echo "โœ… Dependencies installed!" diff --git a/README.md b/README.md index 1c3a05f..1967f77 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,144 @@ -# lambda-rust-http -Minimal AWS Lambda example in Rust serving a simple HTTP response via API Gateway +# Lambda Rust HTTP + +A minimal AWS Lambda example in Rust that responds with **"Hello, world!"** via API Gateway, with developer-friendly Devcontainer setup. + +## ๐Ÿš€ Quick Start + +### Prerequisites + +- Docker Desktop +- VS Code with Dev Containers extension +- AWS CLI configured with appropriate credentials + +### 1. Open in Dev Container + +1. Clone this repository +2. Open in VS Code +3. When prompted, click "Reopen in Container" +4. Wait for the container to build and install dependencies + +### 2. Local Development + +```bash +# Watch for changes and run locally +cargo lambda watch + +# In another terminal, test locally +curl http://localhost:9000/ +# Response: {"message":"Hello, world!"} +``` + +### 3. Build for Production + +```bash +# Build ARM64 binary for AWS Lambda +cargo lambda build --release --arm64 + +# The binary will be created at: +# target/lambda/lambda-rust-http/bootstrap +``` + +### 4. Deploy to AWS + +```bash +# Navigate to infrastructure directory +cd infra + +# Initialize Terraform +terraform init + +# Plan deployment +terraform plan + +# Deploy infrastructure +terraform apply + +# Get the API Gateway URL +terraform output api_gateway_url +``` + +### 5. Test the Deployed Function + +```bash +# Test with the URL from terraform output +curl https://.execute-api..amazonaws.com/prod/ +# Response: {"message":"Hello, world!"} +``` + +## ๐Ÿ—๏ธ Project Structure + +```plaintext +lambda-rust-http/ +โ”œโ”€โ”€ .devcontainer/ # VS Code Dev Container config +โ”‚ โ”œโ”€โ”€ devcontainer.json # Container settings +โ”‚ โ””โ”€โ”€ Dockerfile # Container image +โ”œโ”€โ”€ src/ +โ”‚ โ””โ”€โ”€ main.rs # Lambda function code +โ”œโ”€โ”€ infra/ # Terraform infrastructure +โ”‚ โ”œโ”€โ”€ main.tf # Main infrastructure config +โ”‚ โ”œโ”€โ”€ variables.tf # Variable definitions +โ”‚ โ””โ”€โ”€ outputs.tf # Output values +โ”œโ”€โ”€ Cargo.toml # Rust dependencies +โ””โ”€โ”€ README.md # This file +``` + +## ๐Ÿ”ง Development Workflow + +1. **Local Development**: Use `cargo lambda watch` for hot reloading +2. **Testing**: Test locally before deploying +3. **Build**: Use `cargo lambda build --release --arm64` for production +4. **Deploy**: Use Terraform to deploy infrastructure +5. **Test**: Verify the deployed function works + +## ๐Ÿ“ฆ Build Artifacts + +The build process creates: + +- **Binary**: `target/lambda/lambda-rust-http/bootstrap` (ARM64) +- **Package**: `target/lambda/lambda-rust-http.zip` (for Terraform) + +## ๐ŸŒ API Endpoints + +- **Root**: `GET /` โ†’ Returns `{"message":"Hello, world!"}` +- **Any Path**: `ANY /{proxy+}` โ†’ Same response (handles all routes) + +## ๐Ÿ› ๏ธ Technologies Used + +- **Rust**: Programming language +- **lambda_http**: AWS Lambda HTTP runtime +- **cargo-lambda**: Build tool for Rust Lambda functions +- **Terraform**: Infrastructure as Code +- **Dev Containers**: Consistent development environment + +## ๐Ÿ“š Reference + +This implementation is based on the official AWS sample template: +๐Ÿ‘‰ [**basic-http-function** example in aws-lambda-rust-runtime](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples/basic-http-function) + +## ๐Ÿ” Troubleshooting + +### Common Issues + +1. **Build fails**: Ensure you're using the Dev Container with Rust 1.70+ +2. **Terraform errors**: Check AWS credentials and region configuration +3. **Lambda cold start**: First invocation may be slower + +### Useful Commands + +```bash +# Check Rust version +rustc --version + +# Check cargo-lambda installation +cargo lambda --version + +# Check AWS CLI +aws --version + +# Check Terraform +terraform --version +``` + +## ๐Ÿ“„ License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..780ba18 --- /dev/null +++ b/build.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +set -e + +echo "๐Ÿš€ Building Lambda Rust HTTP function..." + +# Check if cargo-lambda is installed +if ! command -v cargo-lambda &> /dev/null; then + echo "โŒ cargo-lambda not found. Installing..." + cargo install cargo-lambda +fi + +# Clean previous builds +echo "๐Ÿงน Cleaning previous builds..." +cargo clean + +# Build the release binary +echo "๐Ÿ”จ Building ARM64 binary..." +cargo lambda build --release --arm64 + +# Check if binary was created +if [ ! -f "target/lambda/lambda-rust-http/bootstrap" ]; then + echo "โŒ Binary not found at target/lambda/lambda-rust-http/bootstrap" + exit 1 +fi + +echo "โœ… Build successful!" +echo "๐Ÿ“ฆ Binary location: target/lambda/lambda-rust-http/bootstrap" +echo "๐Ÿ“ Binary size: $(du -h target/lambda/lambda-rust-http/bootstrap | cut -f1)" + +# Create deployment package +echo "๐Ÿ“ฆ Creating deployment package..." +cd target/lambda/lambda-rust-http +zip -r lambda-rust-http.zip bootstrap +cd ../../.. + +echo "๐ŸŽ‰ Deployment package ready at: target/lambda/lambda-rust-http/lambda-rust-http.zip" +echo "๐Ÿ“ Package size: $(du -h target/lambda/lambda-rust-http/lambda-rust-http.zip | cut -f1)" +echo "" +echo "๐Ÿš€ Ready to deploy with Terraform!" +echo " cd infra && terraform apply" diff --git a/infra/main.tf b/infra/main.tf new file mode 100644 index 0000000..7380ad9 --- /dev/null +++ b/infra/main.tf @@ -0,0 +1,147 @@ +terraform { + required_version = ">= 1.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + archive = { + source = "hashicorp/archive" + version = "~> 2.0" + } + } +} + +provider "aws" { + region = var.aws_region +} + +# Data source for current AWS account +data "aws_caller_identity" "current" {} + +# Data source for current AWS region +data "aws_region" "current" {} + +# Archive the Lambda function code +data "archive_file" "lambda_zip" { + type = "zip" + source_file = "../target/lambda/bootstrap/bootstrap" + output_path = "../target/lambda/lambda-rust-http.zip" +} + +# IAM role for Lambda execution +resource "aws_iam_role" "lambda_exec" { + name = "lambda-rust-http-exec-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + } + ] + }) +} + +# Attach basic execution role policy +resource "aws_iam_role_policy_attachment" "lambda_basic" { + role = aws_iam_role.lambda_exec.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + +# Lambda function +resource "aws_lambda_function" "main" { + filename = data.archive_file.lambda_zip.output_path + function_name = "lambda-rust-http" + role = aws_iam_role.lambda_exec.arn + handler = "bootstrap" + source_code_hash = data.archive_file.lambda_zip.output_base64sha256 + runtime = "provided.al2023" + architectures = ["arm64"] + + environment { + variables = { + RUST_LOG = "info" + } + } +} + +# API Gateway REST API +resource "aws_api_gateway_rest_api" "main" { + name = "lambda-rust-http-api" +} + +# API Gateway resource +resource "aws_api_gateway_resource" "proxy" { + rest_api_id = aws_api_gateway_rest_api.main.id + parent_id = aws_api_gateway_rest_api.main.root_resource_id + path_part = "{proxy+}" +} + +# API Gateway method for proxy resource +resource "aws_api_gateway_method" "proxy" { + rest_api_id = aws_api_gateway_rest_api.main.id + resource_id = aws_api_gateway_resource.proxy.id + http_method = "ANY" + authorization = "NONE" +} + +# API Gateway method for root resource +resource "aws_api_gateway_method" "proxy_root" { + rest_api_id = aws_api_gateway_rest_api.main.id + resource_id = aws_api_gateway_rest_api.main.root_resource_id + http_method = "ANY" + authorization = "NONE" +} + +# Lambda integration for proxy resource +resource "aws_api_gateway_integration" "lambda" { + rest_api_id = aws_api_gateway_rest_api.main.id + resource_id = aws_api_gateway_resource.proxy.id + http_method = aws_api_gateway_method.proxy.http_method + + integration_http_method = "POST" + type = "AWS_PROXY" + uri = aws_lambda_function.main.invoke_arn +} + +# Lambda integration for root resource +resource "aws_api_gateway_integration" "lambda_root" { + rest_api_id = aws_api_gateway_rest_api.main.id + resource_id = aws_api_gateway_rest_api.main.root_resource_id + http_method = aws_api_gateway_method.proxy_root.http_method + + integration_http_method = "POST" + type = "AWS_PROXY" + uri = aws_lambda_function.main.invoke_arn +} + +# Lambda permission for API Gateway +resource "aws_lambda_permission" "apigw" { + statement_id = "AllowExecutionFromAPIGateway" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.main.function_name + principal = "apigateway.amazonaws.com" + source_arn = "${aws_api_gateway_rest_api.main.execution_arn}/*/*" +} + +# API Gateway deployment +resource "aws_api_gateway_deployment" "main" { + depends_on = [ + aws_api_gateway_integration.lambda, + aws_api_gateway_integration.lambda_root, + ] + + rest_api_id = aws_api_gateway_rest_api.main.id +} + +# API Gateway stage +resource "aws_api_gateway_stage" "main" { + deployment_id = aws_api_gateway_deployment.main.id + rest_api_id = aws_api_gateway_rest_api.main.id + stage_name = "prod" +} diff --git a/infra/outputs.tf b/infra/outputs.tf new file mode 100644 index 0000000..f054585 --- /dev/null +++ b/infra/outputs.tf @@ -0,0 +1,14 @@ +output "api_gateway_url" { + description = "URL of the API Gateway endpoint" + value = aws_api_gateway_stage.main.invoke_url +} + +output "lambda_function_arn" { + description = "ARN of the Lambda function" + value = aws_lambda_function.main.arn +} + +output "lambda_function_name" { + description = "Name of the Lambda function" + value = aws_lambda_function.main.function_name +} diff --git a/infra/variables.tf b/infra/variables.tf new file mode 100644 index 0000000..47be647 --- /dev/null +++ b/infra/variables.tf @@ -0,0 +1,5 @@ +variable "aws_region" { + description = "AWS region to deploy resources" + type = string + default = "ap-southeast-2" +} diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..d627d21 --- /dev/null +++ b/setup.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Setup script for Rust Lambda ARM64 builds +set -e + +echo "๐Ÿš€ Setting up Rust Lambda development environment..." + +# Check if Zig is already installed +if ! command -v zig &> /dev/null; then + echo "๐Ÿ“ฆ Installing Zig 0.13.0..." + + # Download and install Zig + ZIG_VERSION="0.13.0" + ZIG_DIR="zig-linux-x86_64-${ZIG_VERSION}" + + if [ ! -d "$ZIG_DIR" ]; then + curl -L "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz" -o zig.tar.xz + tar -xf zig.tar.xz + rm zig.tar.xz + fi + + # Add Zig to PATH for this session + export PATH="$PWD/$ZIG_DIR:$PATH" + echo "export PATH=\"$PWD/$ZIG_DIR:\$PATH\"" >> ~/.bashrc + + echo "โœ… Zig installed successfully" +else + echo "โœ… Zig already installed: $(zig version)" +fi + +# Check if ARM64 target is installed +if ! rustup target list --installed | grep -q "aarch64-unknown-linux-gnu"; then + echo "๐ŸŽฏ Installing ARM64 Rust target..." + rustup target add aarch64-unknown-linux-gnu + echo "โœ… ARM64 target installed" +else + echo "โœ… ARM64 target already installed" +fi + +# Verify cargo-lambda setup +echo "๐Ÿ” Verifying cargo-lambda setup..." +cargo lambda system + +echo "" +echo "๐ŸŽ‰ Setup complete! You can now run:" +echo " cargo lambda build --release --arm64" +echo "" +echo "๐Ÿ’ก To make Zig available in new terminals, run:" +echo " source ~/.bashrc" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..038fbf7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,25 @@ +use lambda_http::{run, service_fn, Body, Error, Request, Response}; +use serde_json::json; + +async fn function_handler(_request: Request) -> Result, Error> { + let response_body = json!({ + "message": "Hello, world!" + }); + + Ok(Response::builder() + .status(200) + .header("content-type", "application/json") + .body(Body::from(response_body.to_string())) + .unwrap()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_target(false) + .without_time() + .init(); + + run(service_fn(function_handler)).await +}