Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Keep the Docker build context small and platform-agnostic: never ship host
# build artifacts or VCS metadata into the image.
target/
**/target/
.git/
.github/
*.md
20 changes: 20 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: E2E

on:
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
docker-e2e:
name: test
runs-on: ubuntu-latest
if: ${{ !startsWith(github.event.pull_request.title, 'chore:') }}
steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Run Docker e2e (server + client over HTTP)
run: make test-e2e
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ SHELL := /bin/bash

.PHONY: help
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-35s\033[0m %s\n", $$1, $$2}'

.PHONY: clean
Expand Down Expand Up @@ -43,6 +43,12 @@ test-unit: ## Run unit tests
test-integration: ## Run integration tests
cargo test --features integration --test '*_integration' -- --nocapture

.PHONY: test-e2e
test-e2e: ## Run the Docker end-to-end test (generated server + client over HTTP)
@compose="docker compose -f crates/oapi-codegen/tests/integration/docker-compose.yml"; \
trap 'code=$$?; $$compose down --remove-orphans --volumes; exit $$code' EXIT; \
$$compose up --build --exit-code-from client --abort-on-container-exit

.PHONY: update-generated
update-generated: ## Refresh generated files from the coverage fixtures
UPDATE_GENERATED=1 cargo test -p oapi-codegen --test coverage
Expand All @@ -52,7 +58,8 @@ generate-example: ## Regenerate the composed bookstore example from its OpenAPI
cd examples/bookstore && \
cargo run -q -p oapi-codegen -- schemas/common.yaml --config oapi-codegen-common.yaml && \
cargo run -q -p oapi-codegen -- schemas/catalog.yaml --config oapi-codegen-catalog.yaml && \
cargo run -q -p oapi-codegen -- openapi.yaml --config oapi-codegen-server.yaml
cargo run -q -p oapi-codegen -- openapi.yaml --config oapi-codegen-server.yaml && \
cargo run -q -p oapi-codegen -- openapi.yaml --config oapi-codegen-client.yaml

.PHONY: verify-generated
verify-generated: ## Regenerate all generated code and fail if it drifts from what is committed
Expand Down
11 changes: 11 additions & 0 deletions crates/oapi-codegen/tests/integration/client/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Compile the generated blocking `reqwest` client and its e2e test, then run the
# test against the server named by BOOKSTORE_BASE_URL (set in docker-compose.yml).
# The image keeps the full toolchain because it runs `cargo test` at container
# start; pre-building with `--no-run` means startup goes straight to the tests.
# The build context is the repository root (see docker-compose.yml).

FROM rust:1.96-bookworm AS builder
WORKDIR /src
COPY . .
RUN cargo test --no-run --locked -p bookstore-example --features client --test e2e
CMD ["cargo", "test", "--locked", "-p", "bookstore-example", "--features", "client", "--test", "e2e", "--", "--nocapture"]
29 changes: 29 additions & 0 deletions crates/oapi-codegen/tests/integration/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: bookstore-e2e

# End-to-end harness: the `server` service runs the generated axum server and the
# `client` service compiles the generated reqwest client + integration test and
# drives every bookstore endpoint over HTTP. Run from the repository root with:
#
# docker compose -f crates/oapi-codegen/tests/integration/docker-compose.yml \
# up --build --exit-code-from client --abort-on-container-exit
#
# The overall exit status is the client's, so CI fails iff the e2e tests fail.

services:
server:
build:
context: ../../../..
dockerfile: crates/oapi-codegen/tests/integration/server/Dockerfile
environment:
BOOKSTORE_ADDR: 0.0.0.0:8080
expose:
- "8080"

client:
build:
context: ../../../..
dockerfile: crates/oapi-codegen/tests/integration/client/Dockerfile
depends_on:
- server
environment:
BOOKSTORE_BASE_URL: http://server:8080
14 changes: 14 additions & 0 deletions crates/oapi-codegen/tests/integration/server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Build the bookstore server binary from the workspace, then run it from a slim
# runtime image. The build context is the repository root (see docker-compose.yml).

FROM rust:1.96-bookworm AS builder
WORKDIR /src
COPY . .
RUN cargo build --release --locked -p bookstore-example --bin bookstore-server

FROM debian:bookworm-slim AS runtime
WORKDIR /app
COPY --from=builder /src/target/release/bookstore-server /usr/local/bin/bookstore-server
ENV BOOKSTORE_ADDR=0.0.0.0:8080
EXPOSE 8080
CMD ["bookstore-server"]
19 changes: 19 additions & 0 deletions examples/bookstore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,27 @@ publish = false
[lints]
workspace = true

[features]
# Compile the generated blocking `reqwest` client (`restclient`) and the e2e
# integration test that drives it. Kept optional so the default build stays lean
# and does not pull in `reqwest`; the Docker e2e image builds with this feature.
client = ["dep:reqwest", "dep:percent-encoding"]

[dependencies]
axum = { version = "0.8.9", features = ["multipart"] }
axum-extra = { version = "0.12.6", features = ["query"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.150"
tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros", "net"] }

reqwest = { version = "0.12.28", default-features = false, features = ["blocking", "json", "multipart"], optional = true }
Comment thread
dotkas marked this conversation as resolved.
percent-encoding = { version = "2.3.2", optional = true }

[dev-dependencies]
# Used only by the e2e coverage guard (`tests/e2e_coverage.rs`) to parse
# `openapi.yaml` and assert every operation has a registered e2e test.
serde_yaml = "0.9.34+deprecated"

[[bin]]
name = "bookstore-server"
path = "src/bin/server.rs"
Loading