diff --git a/.github/workflows/release-gate.yml b/.github/workflows/release-gate.yml new file mode 100644 index 0000000..3619fe1 --- /dev/null +++ b/.github/workflows/release-gate.yml @@ -0,0 +1,120 @@ +name: v1 Release Gate + +on: + workflow_dispatch: + push: + branches: [ main, master ] + paths: + - 'include/**' + - 'src/**' + - 'tests/**' + - 'examples/**' + - 'cmake/**' + - 'CMakeLists.txt' + - 'vcpkg.json' + - 'vcpkg-configuration.json' + - '.github/workflows/release-gate.yml' + pull_request: + branches: [ main, master ] + paths: + - 'include/**' + - 'src/**' + - 'tests/**' + - 'examples/**' + - 'cmake/**' + - 'CMakeLists.txt' + - 'vcpkg.json' + - 'vcpkg-configuration.json' + - '.github/workflows/release-gate.yml' + +jobs: + sanitizers: + name: ASan + UBSan (Clang, C++17) + runs-on: ubuntu-latest + env: + ASAN_OPTIONS: detect_leaks=1:halt_on_error=1 + UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1 + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Configure Sanitizer Build + shell: bash + run: | + cmake -B build-sanitize \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCPP_REQUEST_ENABLE_SANITIZERS=ON \ + -DCPP_REQUEST_WARNINGS_AS_ERRORS=ON \ + -DCPP_REQUEST_BUILD_BENCHMARKS=OFF \ + -DCPP_REQUEST_BUILD_EXAMPLES=ON + + - name: Build Sanitizer Configuration + shell: bash + run: | + cmake --build build-sanitize --config Debug + + - name: Run Sanitized Tests + shell: bash + run: | + ctest \ + --test-dir build-sanitize \ + --build-config Debug \ + --output-on-failure \ + -E '^PackageConsumer\.InstallAndUse$' + + cxx20-consumer: + name: C++20 Compatibility + Installed Consumer + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Configure C++20 Build + shell: bash + run: | + cmake -B build-cxx20 \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=20 \ + -DCMAKE_CXX_STANDARD_REQUIRED=ON \ + -DCPP_REQUEST_WARNINGS_AS_ERRORS=ON \ + -DCPP_REQUEST_BUILD_BENCHMARKS=OFF \ + -DCPP_REQUEST_BUILD_EXAMPLES=ON + + - name: Build and Test C++20 Configuration + shell: bash + run: | + cmake --build build-cxx20 --config Release + ctest \ + --test-dir build-cxx20 \ + --build-config Release \ + --output-on-failure + + - name: Install Package + shell: bash + run: | + cmake --install build-cxx20 \ + --config Release \ + --prefix "$PWD/stage" + + - name: Configure Installed C++20 Consumer + shell: bash + run: | + cmake \ + -S tests/package_consumer \ + -B build-consumer-cxx20 \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="$PWD/stage" \ + -DCMAKE_CXX_STANDARD=20 \ + -DCMAKE_CXX_STANDARD_REQUIRED=ON + + - name: Build and Run Installed C++20 Consumer + shell: bash + run: | + cmake --build build-consumer-cxx20 --config Release + ctest \ + --test-dir build-consumer-cxx20 \ + --build-config Release \ + --output-on-failure diff --git a/docs/release/v1.0-acceptance.md b/docs/release/v1.0-acceptance.md new file mode 100644 index 0000000..df8ce67 --- /dev/null +++ b/docs/release/v1.0-acceptance.md @@ -0,0 +1,137 @@ +# v1.0 Acceptance Matrix + +## Status + +- Project: `cpp_request` +- Target: v1.0 release candidate +- Current project version during hardening: v0.9.0 +- Scope: synchronous HTTP/1.1 over plaintext TCP +- Purpose: map frozen v1 requirements to concrete implementation/test/CI evidence + +This document is a release gate, not a replacement for `docs/requirements/functional.md` or `docs/requirements/non-functional.md`. A requirement remains authoritative in those frozen documents. This matrix records how the repository verifies it before the version is promoted to v1.0.0. + +A v1.0 version bump/tag must not happen until all required CI checks are green and no release-blocking correctness issue remains. + +--- + +## Functional requirement coverage + +| Requirement area | Requirement IDs | Primary evidence | Gate status | +| --- | --- | --- | --- | +| HTTP/1.1 + methods | REQ-HTTP-001, REQ-HTTP-002 | `request_serializer_test.cpp`, `client_test.cpp` | Covered | +| Custom headers + query | REQ-HTTP-003, REQ-HTTP-004 | `headers_test.cpp`, `request_test.cpp`, `query_test.cpp` | Covered | +| In-memory request body | REQ-HTTP-005 | `request_serializer_test.cpp`, `client_test.cpp` | Covered | +| URL / HTTP-only scheme | REQ-URL-001, REQ-URL-002 | `url_test.cpp`, `client_test.cpp` | Covered | +| DNS / IPv4 / IPv6 / native sockets | REQ-NET-001..004 | `resolver_test.cpp`, `native_socket_test.cpp`, `tcp_connection_test.cpp` | Covered | +| TCP establishment + reuse + close semantics | REQ-CONN-001..003 | `tcp_connection_test.cpp`, `client_reuse_test.cpp`, response-parser reuse tests | Covered | +| RAII socket ownership | REQ-CONN-004 | `native_socket_test.cpp`, timeout/failure-path connection tests | Covered | +| Status line + headers | REQ-RESP-001, REQ-RESP-002 | `response_parser_test.cpp` | Covered | +| Content-Length + chunked + in-memory response | REQ-RESP-003..005 | `response_parser_test.cpp`, `chunked_decoder_test.cpp`, `response_limits_test.cpp` | Covered | +| HEAD semantics | REQ-RESP-006 | `response_parser_test.cpp`, client loopback tests | Covered | +| Redirects | REQ-REDIR-001..003 | `redirect_test.cpp`, `client_redirect_test.cpp` | Covered | +| Connect timeout | REQ-TIME-001 | `tcp_connection_test.cpp` (`ConnectTimeout`) | Covered | +| Read timeout | REQ-TIME-002 | `tcp_io_test.cpp` (`ReadTimeoutClosesConnection`) | Covered | +| Write timeout | REQ-TIME-003 | `tcp_io_test.cpp` (`WriteTimeoutClosesConnection`) | Covered | +| Structured Result / errors | REQ-ERR-001..003 | `result_test.cpp`, protocol/transport error tests, `docs/api/error-model.md` | Covered | +| Stateful client + convenience helpers | REQ-API-001, REQ-API-002 | `client_test.cpp`, public examples | Covered | +| Native socket encapsulation | REQ-API-003 | public-header isolation target + installed consumer | Release gate | +| No mandatory PImpl | REQ-API-004 | public type definitions / implementation inspection | Covered | +| Thread-safety contract | REQ-API-005 | `README.md`, `docs/getting-started.md`, `docs/api/public-api.md` | Covered | +| `std::string_view` contract | REQ-API-006 | public headers + `docs/api/lifetime.md` | Covered | + +--- + +## Non-functional requirement coverage + +| Requirement area | Requirement IDs | Primary evidence | Gate status | +| --- | --- | --- | --- | +| C++17 minimum | REQ-COMP-001 | target `cxx_std_17`, normal CI matrix | Covered | +| Newer-standard compatibility | REQ-COMP-002 | v1 release gate C++20 build + installed consumer | Release gate | +| Zero third-party runtime dependency | REQ-DEP-001 | native socket implementation, install-tree consumer | Covered | +| Dev dependencies do not leak | REQ-DEP-002, REQ-BUILD-003 | package export + `PackageConsumer.InstallAndUse` | Covered | +| Allocation/copy discipline | REQ-PERF-001, REQ-PERF-002 | borrowed request body boundary, parser design, benchmarkable hot paths | Reviewed | +| No unnecessary virtual indirection | REQ-PERF-003 | architecture/source inspection | Reviewed | +| Connection reuse | REQ-PERF-004 | `client_reuse_test.cpp`, reuse-vs-reconnect benchmark | Covered | +| Benchmarkable hot paths | REQ-PERF-005 | benchmark suite + Benchmark Smoke workflow | Covered | +| Windows / Linux | REQ-PORT-001, REQ-PORT-002 | Debug/Release CI jobs | Covered | +| macOS compatibility | REQ-PORT-003 | Debug/Release CI jobs | Covered | +| Platform isolation | REQ-PORT-004 | `src/platform`, `src/net`, `src/http` boundaries + public-header gate | Covered | +| Deterministic cleanup | REQ-REL-001 | RAII tests + sanitizer gate + timeout-close tests | Release gate | +| Predictable errors / no downgrade | REQ-REL-002, REQ-REL-003 | result/error tests, HTTPS rejection tests | Covered | +| Bounded waits | REQ-REL-004 | connect/read/write timeout tests | Covered | +| Small public surface | REQ-APIQ-001 | eight installed public headers + public-header isolation gate | Release gate | +| Value-oriented types | REQ-APIQ-002 | public type inspection | Reviewed | +| Move support | REQ-APIQ-003 | client move tests / resource ownership tests | Covered | +| Thread-safety docs | REQ-APIQ-004 | README / getting-started / public API docs | Covered | +| CMake build entry point | REQ-BUILD-001 | root CMake + modular build options | Covered | +| Installable package | REQ-BUILD-002 | install/export config + installed consumer | Covered | +| Unit tests | REQ-TEST-001 | protocol/URL/header/chunk/parser tests | Covered | +| Integration tests | REQ-TEST-002 | local loopback client/reuse/redirect/timeout tests | Covered | +| No public HTTP service in required tests | REQ-TEST-003 | loopback-controlled networking tests | Covered | +| Cross-platform CI | REQ-TEST-004 | Windows/Linux/macOS matrix | Covered | +| Public API documentation | REQ-DOC-001 | README, getting-started, `docs/api/*` | Covered | +| Scope documentation | REQ-DOC-002 | README + frozen requirements | Covered | +| Architecture documentation | REQ-DOC-003 | `docs/architecture/*` | Covered | + +--- + +## Automated v1 release gates + +### Existing gates + +1. `C++ CI Build` + - Windows, Linux, macOS. + - Debug and Release. + - unit + loopback integration tests. + - install-tree consumer test. + - examples compile as part of the top-level build. + +2. `Benchmark Smoke` + - Windows, Linux, macOS Release builds. + - all v0.9 benchmark executables build and execute locally. + +### Added by release hardening + +3. `v1 Release Gate / ASan + UBSan (Clang, C++17)` + - Debug Clang build. + - warnings as errors. + - AddressSanitizer + UndefinedBehaviorSanitizer. + - leak detection enabled on Linux. + - unit and loopback tests run under sanitizers. + - the install-consumer test is excluded from this sanitizer run because an independently configured consumer would otherwise need matching sanitizer link flags. + +4. `v1 Release Gate / C++20 Compatibility + Installed Consumer` + - full C++20 project build and tests. + - warnings as errors. + - installs the package to a staging prefix. + - separately configures, builds, and runs the installed consumer as C++20. + +5. Public-header isolation compile target + - each public header is compiled in its own translation unit. + - only `${PROJECT_SOURCE_DIR}/include` is available. + - no `src/` include path is supplied. + - catches missing direct includes and accidental internal/platform dependencies. + +--- + +## Manual release review + +The following checks remain deliberate human review items before the final v1.0.0 bump/tag: + +- confirm no unresolved correctness issue can corrupt HTTP framing or connection reuse, +- review performance claims against the committed benchmark suite rather than intuition, +- review public API/lifetime docs against current headers, +- confirm the installed target has no GTest/Google Benchmark dependency, +- confirm no post-v1 feature slipped into the frozen scope, +- review release notes/versioning/tagging metadata. + +--- + +## Promotion rule + +When all automated release-gate checks are green and the manual review above has no blocker, the next change should be a focused release PR that: + +1. changes the project version from `0.9.0` to `1.0.0`, +2. updates roadmap/release status to v1.0 complete, +3. adds final release notes/changelog metadata if desired, +4. creates the v1.0 tag/release only after that release PR is merged. diff --git a/docs/roadmap.md b/docs/roadmap.md index e0ecf82..2bcef22 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -7,21 +7,12 @@ - Current development version: v0.9.0 - Language baseline: C++17 - Protocol scope: synchronous HTTP/1.1 over plaintext TCP -- Last roadmap review: 2026-09-15 +- Current milestone: v1.0 release hardening +- Last roadmap review: 2026-09-16 -This document is the implementation-order source of truth for the v1 release. The frozen functional and non-functional requirements remain authoritative for **what** v1 must provide; this roadmap records milestone completion and the remaining release gate. +The frozen functional and non-functional requirements remain authoritative for **what** v1 must provide. This roadmap records implementation milestones and the remaining release work. -Version labels are development milestones and do not require a public release tag unless explicitly decided. - ---- - -## Status Legend - -- โœ… **Complete** โ€” merged implementation exists and the milestone exit criteria are satisfied. -- ๐Ÿšง **In progress** โ€” active implementation or finalization work exists. -- โณ **Planned** โ€” required work has not started. -- ๐Ÿงช **Release gate** โ€” integrated verification required before v1.0. -- โžก๏ธ **Post-v1** โ€” intentionally outside the frozen v1 scope. +The detailed v1 acceptance evidence is maintained in [`docs/release/v1.0-acceptance.md`](release/v1.0-acceptance.md). --- @@ -31,378 +22,128 @@ Version labels are development milestones and do not require a public release ta | --- | --- | --- | | v0.1 | Requirements, architecture, API/error contracts | โœ… Complete | | v0.2 | URL, platform sockets, DNS, TCP transport | โœ… Complete | -| v0.3 | HTTP request data model and serialization | โœ… Complete | +| v0.3 | HTTP request model and serialization | โœ… Complete | | v0.4 | HTTP response parsing and body framing | โœ… Complete | -| v0.5 | Public `Client` end-to-end execution path | โœ… Complete | +| v0.5 | Public `Client` execution path | โœ… Complete | | v0.6 | HTTP/1.1 connection reuse / keep-alive | โœ… Complete | | v0.7 | Redirect handling | โœ… Complete | | v0.8 | Protocol/API correctness hardening | โœ… Complete | -| v0.9 | Packaging, benchmarks, examples, documentation | โœ… Complete on merge of PR #29 | -| v1.0 | Release hardening and acceptance gate | ๐Ÿงช Next | - ---- - -# v0.1 โ€” Specification and Architecture Baseline โœ… - -Goal: freeze the v1 product boundary before implementation expands. - -Completed: - -- frozen functional and non-functional requirements, -- layered Public API / HTTP / Transport / Platform architecture, -- public API, lifetime, error, and `Result` contracts, -- C++17 minimum, -- synchronous-only v1, -- no PImpl requirement, -- HTTPS/TLS explicitly outside v1. - -Historical references: - -- PR #2 โ€” frozen v1 requirements -- PR #3 โ€” architecture boundaries -- PR #4 โ€” public API contract -- PR #5 โ€” error and `Result` contracts - -Exit criteria: **complete**. - ---- - -# v0.2 โ€” Native Transport Foundation โœ… - -Goal: provide a portable bounded TCP byte stream without leaking native sockets into the public API. - -Completed: - -- URL parsing and HTTP-only scheme validation, -- IPv4 and IPv6 endpoints, -- Winsock/POSIX native socket RAII, -- network runtime initialization, -- OS DNS resolution, -- timed connect/read/write, -- endpoint fallback, -- partial-I/O handling, -- SIGPIPE-safe POSIX behavior, -- normalized transport errors. - -Historical references: - -- PR #6 โ€” URL parser -- PR #7 โ€” native socket RAII -- PR #8 โ€” DNS resolver and endpoints -- PR #9 โ€” timed TCP connect -- PR #10 โ€” timed TCP read/write - -Exit criteria: **complete**. - ---- - -# v0.3 โ€” HTTP Request Model and Serialization โœ… - -Goal: represent and serialize all v1 request methods while avoiding unnecessary payload copies. - -Completed: - -- ordered owning `Headers`, -- case-insensitive lookup and duplicate fields, -- borrowed request URL/body, -- GET / HEAD / POST / PUT / PATCH / DELETE, -- request-line and header serialization, -- automatic `Host`, -- IPv6 Host formatting, -- Content-Length generation/validation, -- request-header syntax and injection validation. - -Historical references: - -- PR #11 โ€” `Headers` and `Request` -- PR #12 โ€” request serializer - -Exit criteria: **complete**. - ---- - -# v0.4 โ€” HTTP Response Parser and Framing โœ… - -Goal: parse HTTP/1.1 incrementally and independently of TCP packet boundaries. - -Completed: - -- owning `Response`, -- status-line and header parsing, -- interim 1xx handling, -- HEAD/no-body semantics, -- Content-Length framing, -- close-delimited framing, -- chunked transfer decoding, -- chunk extension/trailer validation, -- EOF and framing-conflict detection, -- connection-reuse eligibility signals, -- preservation of pending bytes belonging to the following response. - -Historical references: - -- PR #13 โ€” incremental response parser -- PR #14 โ€” chunked transfer decoder - -Exit criteria: **complete**. - ---- - -# v0.5 โ€” Public Client Core โœ… - -Goal: connect serialization, DNS, TCP, parser, and public result handling into one synchronous request path. - -Completed: - -- `Client::request(const Request&)`, -- member GET / HEAD / POST / PUT / PATCH / DELETE helpers, -- equivalent one-shot free helpers, -- connect/read/write timeout configuration, -- loopback integration coverage, -- unsupported HTTPS rejection before networking. - -Historical reference: - -- PR #15 โ€” public `Client` execution path - -Exit criteria: **complete**. +| v0.9 | Packaging, benchmarks, examples, documentation | โœ… Complete | +| v1.0 | Release hardening and acceptance gate | ๐Ÿšง In progress | --- -# v0.6 โ€” Connection Reuse / Keep-Alive โœ… - -Goal: make `Client` genuinely stateful for sequential same-origin HTTP/1.1 traffic. - -Completed: +## Completed implementation milestones -- one retained eligible connection, -- effective host+port origin identity, -- reuse only after complete reusable responses, -- forced close for `Connection: close`, close-delimited, upgrade, and failed states, -- origin-change reconnect, -- no hidden automatic retry after stale keep-alive failure, -- move-only client ownership. +### v0.1 โ€” Specification and architecture -Historical reference: +Frozen v1 requirements, architecture boundaries, public API/error/lifetime contracts, C++17 baseline, synchronous-only execution, no mandatory PImpl, and explicit HTTPS/TLS exclusion. -- PR #20 โ€” HTTP/1.1 connection reuse +References: PR #2, #3, #4, #5. -Exit criteria: **complete**. +### v0.2 โ€” Native transport foundation ---- - -# v0.7 โ€” Redirect Handling โœ… - -Goal: implement bounded redirects without expanding beyond plaintext HTTP. +URL parsing, native socket RAII, DNS, IPv4/IPv6 endpoints, timed TCP connect/read/write, endpoint fallback, partial I/O, and platform error normalization. -Completed: +References: PR #6โ€“#10. -- configurable finite redirect following, -- 301 / 302 / 303 / 307 / 308 handling, -- method/body rewrite policy, -- absolute and relative Location resolution, -- same-origin reuse and cross-origin reconnect, -- sensitive header stripping across origins, -- unsupported redirect-scheme rejection, -- redirect-specific structured errors. +### v0.3 โ€” Request model and serializer -Historical reference: +Ordered headers, request methods, borrowed request URL/body, Host generation, Content-Length behavior, custom headers, query parameters, and request injection validation. -- PR #21 โ€” bounded HTTP redirect handling - -Exit criteria: **complete**. - ---- +References: PR #11, #12, #23. -# v0.8 โ€” Protocol and API Correctness Hardening โœ… +### v0.4 โ€” Response parser and framing -Goal: close correctness and bounded-resource gaps before presenting the API as release-candidate quality. +Incremental status/header parsing, Content-Length, close-delimited and chunked bodies, HEAD/1xx/204/205/304 semantics, trailer/chunk validation, EOF handling, and framing-conflict rejection. -## Result hardening +References: PR #13, #14, #24. -Completed: +### v0.5 โ€” Public Client -- explicit variant-index state access, -- no hidden `bad_variant_access` from noexcept accessors, -- explicit success/failure factories, -- unambiguous `Result`, -- move-only payload support. +Stateful synchronous `Client`, one-shot helpers, configurable connect/read/write timeouts, structured errors, and loopback integration coverage. -Reference: PR #22. +Reference: PR #15. -## URL and query correctness +### v0.6 โ€” Connection reuse -Completed: +Single eligible retained connection, same-origin reuse, forced close rules, origin-change reconnect, stale keep-alive handling without hidden retry, and move-only resource ownership. -- owned appended query parameters, -- insertion-order/repeated-key preservation, -- percent encoding, -- raw-query preservation, -- percent-escape validation, -- ASCII-only scheme handling, -- stronger IPv6 literal validation. +Reference: PR #20. -Reference: PR #23. +### v0.7 โ€” Redirects -## HTTP framing correctness +Bounded configurable redirects, HTTP redirect method/body policy, relative target resolution, sensitive-header stripping across origins, and explicit unsupported-scheme handling. -Completed: +Reference: PR #21. -- safe framing precedence, -- Transfer-Encoding + Content-Length conflict rejection, -- 1xx/204/205/304/HEAD framing review, -- strict chunk-extension and trailer validation, -- correct empty-body request Content-Length behavior, -- explicit timeout/EOF semantics, -- CI-exposed lifetime bug fixes. +### v0.8 โ€” Correctness and resource hardening -Reference: PR #24. +`Result` hardening, URL/query correctness, HTTP framing review, explicit timeout/EOF semantics, response resource limits, deterministic boundary tests, and connection invalidation after parsing/resource failures. -## Response resource bounds +References: PR #22โ€“#25. -Completed: +### v0.9 โ€” Consumability and measurement -- public `ResponseLimits`, -- default 64 KiB head limit, -- default 64 MiB decoded body limit, -- default 8 KiB chunk-line limit, -- default 64 KiB trailer limit, -- early Content-Length rejection, -- bounded close-delimited and chunked accumulation, -- `ResponseLimitExceeded`, -- zero treated as a real limit, -- deterministic boundary tests, -- failed limited responses never leave the connection reusable. +Installable CMake package, stable `cpp_request::cpp_request` target, install-tree consumer test, benchmark suite and smoke CI, modular CMake architecture, examples, README, and getting-started documentation. -Reference: PR #25. - -v0.8 exit criteria: **complete**. +References: PR #26โ€“#29. --- -# v0.9 โ€” Packaging, Benchmarks, Examples, and Documentation โœ… - -Goal: make the library consumable, measurable, and understandable before the release gate. - -## Packaging โœ… - -Completed: - -- public header installation, -- library installation/export, -- `cpp_requestConfig.cmake` and version config, -- stable installed target `cpp_request::cpp_request`, -- install-tree consumer test, -- no GTest/Google Benchmark leakage into consumer package metadata. - -Reference: PR #26. - -## Benchmarks โœ… - -Executable benchmarks now cover: - -- request serialization, -- response/header parsing, -- chunked decoding, -- end-to-end local loopback request overhead, -- connection reuse vs reconnect. - -The benchmark suite is opt-in and has a dedicated Release smoke matrix across Windows, Linux, and macOS. - -Reference: PR #27. +# v1.0 โ€” Release Hardening and Acceptance Gate ๐Ÿšง -## Build-system integration โœ… +Goal: verify the frozen requirements as one coherent release candidate without adding new feature scope. -Completed: +## Automated verification -- modular target-scoped CMake configuration, -- project version aligned to v0.9.0, -- namespaced build helpers, -- centralized test/benchmark/example/install modules, -- clean install/export ownership, -- source-tree-safe configure behavior, -- development tooling cleanup. - -Reference: PR #28. - -## Examples and user documentation โœ… - -Completed by PR #29: - -- root README, -- minimal GET example, -- POST/custom-header/query example, -- configured reusable Client example, -- timeout configuration, -- redirect configuration, -- response-limit configuration, -- structured error handling, -- explicit thread-safety statement, -- explicit HTTP-only / HTTPS-TLS exclusion, -- install/consumer instructions, -- getting-started guide aligned with the implemented public API. - -Existing API-specific documentation remains authoritative for detailed contracts: - -- `docs/api/public-api.md`, -- `docs/api/error-model.md`, -- `docs/api/result.md`, -- `docs/api/lifetime.md`, -- `docs/api/response-limits.md`. - -v0.9 exit criteria: **complete when PR #29 merges**. - ---- - -# v1.0 โ€” Release Hardening and Acceptance Gate ๐Ÿงช - -Goal: verify the frozen requirements as one coherent release candidate. - -Required verification: +The release candidate must have: - Windows Debug/Release CI green, - Linux Debug/Release CI green, - macOS Debug/Release CI green, -- C++17 build validated, -- all unit and loopback integration tests green, -- no required test depends on public internet access, -- sanitizer configuration reviewed and exercised where supported, -- deterministic socket cleanup verified on failure paths, -- connect/read/write timeout coverage present, -- connection reuse and redirect coverage present, +- C++17 build/test coverage, +- C++20 compatibility coverage, +- ASan + UBSan test execution where supported, +- warnings-as-errors release-gate builds, +- unit and loopback integration tests green, +- no required HTTP test depending on a public service, - install-tree consumer test green, -- benchmark targets build and run, -- examples build, -- public headers reviewed for internal/platform leakage, -- documentation checked against the frozen v1 scope, -- release versioning/tagging decision finalized. +- C++20 installed-consumer test green, +- benchmark smoke jobs green, +- examples compiling, +- every public header compiling independently without `src/` includes. -## v1.0 Definition of Done +## Required review -v1.0 is ready only when: +Before v1.0.0 promotion: -1. every **MUST** functional requirement is implemented or explicitly revised first, -2. every **MUST** non-functional requirement has a concrete verification path, -3. no known correctness bug can corrupt an HTTP message boundary or reuse an invalid connection, -4. expected URL/network/protocol/resource failures remain representable through structured errors, -5. the installed library has zero third-party runtime dependency, -6. public API and lifetime contracts match implementation, -7. benchmarks exist for any performance claims the project intends to make, -8. all release-gate CI and documentation checks pass. +- every MUST functional requirement must have implementation/test evidence, +- every MUST non-functional requirement must have a verification path, +- no known bug may corrupt HTTP message framing or reuse an invalid connection, +- expected URL/network/protocol/resource failures must remain structured, +- the installed library must have zero third-party runtime dependency, +- public API/lifetime/error documentation must match implementation, +- performance claims must be grounded in the benchmark suite, +- release version/tag metadata must be finalized. ---- +The requirement-to-evidence matrix lives in [`docs/release/v1.0-acceptance.md`](release/v1.0-acceptance.md). -# Next Step +## Promotion sequence ```text -v0.9 examples + docs merge +v1.0 hardening gates green + โ†“ +manual acceptance review โ†“ -v1.0 integrated release hardening +focused 0.9.0 โ†’ 1.0.0 release PR โ†“ -v1.0 release candidate +merge + โ†“ +v1.0 tag / release ``` -No new feature should enter the v1 release path unless a frozen requirement is explicitly changed. +The current hardening branch intentionally keeps the project version at `0.9.0`. The version becomes `1.0.0` only after the acceptance gate is satisfied. --- @@ -411,10 +152,8 @@ No new feature should enter the v1 release path unless a frozen requirement is e The following remain intentionally outside v1.0: - HTTPS / TLS -- asynchronous API -- coroutines -- HTTP/2 -- HTTP/3 +- asynchronous API and coroutines +- HTTP/2 and HTTP/3 - WebSocket - proxy support - cookie jar @@ -426,34 +165,10 @@ The following remain intentionally outside v1.0: - automatic retries - response cache -These features must not delay v1.0 unless the frozen requirements are explicitly revised. +No item above should enter the v1 release path unless the frozen requirements are explicitly revised first. --- -# Post-v1 Direction โžก๏ธ - -Potential future work includes: - -- TLS/HTTPS transport abstraction, -- asynchronous/coroutine execution, -- streaming request/response bodies, -- generalized multi-origin connection pooling, -- proxy support, -- cookie management, -- compression/decompression, -- retry policies, -- HTTP/2 and later protocol exploration. - -Every post-v1 item should receive its own requirements/design work before implementation. - ---- - -# Roadmap Maintenance Rule - -Update this roadmap whenever: +# Post-v1 Direction -- a v1 MUST requirement changes, -- a milestone completes, -- implementation order materially changes, -- a newly discovered correctness blocker becomes release-critical, -- a feature moves into or out of v1 scope. +Potential future work includes TLS/HTTPS, async/coroutine execution, streaming bodies, multi-origin connection pooling, proxy/cookie support, compression, retry policies, and later HTTP versions. Each post-v1 feature should receive its own requirements/design work before implementation. diff --git a/src/core/url.cpp b/src/core/url.cpp index 1d4d6d2..6228cee 100644 --- a/src/core/url.cpp +++ b/src/core/url.cpp @@ -39,8 +39,9 @@ namespace { } [[nodiscard]] bool contains_forbidden_url_byte(std::string_view input) noexcept { - for (const unsigned char ch : input) { - if (ch <= 0x20 || ch == 0x7f) { + for (const char ch : input) { + const auto byte = static_cast(ch); + if (byte <= 0x20u || byte == 0x7fu) { return true; } } diff --git a/src/http/chunked_decoder.cpp b/src/http/chunked_decoder.cpp index 3aa0bdd..ea2d28e 100644 --- a/src/http/chunked_decoder.cpp +++ b/src/http/chunked_decoder.cpp @@ -78,7 +78,8 @@ namespace { return false; } - for (const unsigned char ch : name) { + for (const char raw_ch : name) { + const auto ch = static_cast(raw_ch); if (!is_tchar(ch)) { return false; } @@ -87,7 +88,8 @@ namespace { } [[nodiscard]] bool valid_field_value(std::string_view value) noexcept { - for (const unsigned char ch : value) { + for (const char raw_ch : value) { + const auto ch = static_cast(raw_ch); if (ch == '\t') { continue; } diff --git a/src/http/request_serializer.cpp b/src/http/request_serializer.cpp index 2b66116..cad414b 100644 --- a/src/http/request_serializer.cpp +++ b/src/http/request_serializer.cpp @@ -90,15 +90,16 @@ namespace { void append_percent_encoded(std::string& output, std::string_view input) { constexpr char kHex[] = "0123456789ABCDEF"; - for (const unsigned char ch : input) { - if (is_unreserved(ch)) { - output.push_back(static_cast(ch)); + for (const char ch : input) { + const auto byte = static_cast(ch); + if (is_unreserved(byte)) { + output.push_back(ch); continue; } output.push_back('%'); - output.push_back(kHex[(ch >> 4) & 0x0f]); - output.push_back(kHex[ch & 0x0f]); + output.push_back(kHex[(byte >> 4) & 0x0f]); + output.push_back(kHex[byte & 0x0f]); } } @@ -131,8 +132,9 @@ void append_query_params( return false; } - for (const unsigned char ch : name) { - if (!is_tchar(ch)) { + for (const char ch : name) { + const auto byte = static_cast(ch); + if (!is_tchar(byte)) { return false; } } @@ -140,11 +142,12 @@ void append_query_params( } [[nodiscard]] bool valid_header_value(std::string_view value) noexcept { - for (const unsigned char ch : value) { - if (ch == '\t') { + for (const char ch : value) { + const auto byte = static_cast(ch); + if (byte == '\t') { continue; } - if (ch < 0x20 || ch == 0x7f) { + if (byte < 0x20u || byte == 0x7fu) { return false; } } diff --git a/src/http/response_parser.cpp b/src/http/response_parser.cpp index 8a34230..d8cd808 100644 --- a/src/http/response_parser.cpp +++ b/src/http/response_parser.cpp @@ -82,8 +82,9 @@ struct FramingInfo { return false; } - for (const unsigned char ch : name) { - if (!is_tchar(ch)) { + for (const char ch : name) { + const auto byte = static_cast(ch); + if (!is_tchar(byte)) { return false; } } @@ -91,11 +92,12 @@ struct FramingInfo { } [[nodiscard]] bool valid_field_value(std::string_view value) noexcept { - for (const unsigned char ch : value) { - if (ch == '\t') { + for (const char ch : value) { + const auto byte = static_cast(ch); + if (byte == '\t') { continue; } - if (ch < 0x20 || ch == 0x7f) { + if (byte < 0x20u || byte == 0x7fu) { return false; } } @@ -141,11 +143,12 @@ struct FramingInfo { } [[nodiscard]] bool valid_reason_phrase(std::string_view reason) noexcept { - for (const unsigned char ch : reason) { - if (ch == '\t') { + for (const char ch : reason) { + const auto byte = static_cast(ch); + if (byte == '\t') { continue; } - if (ch < 0x20 || ch == 0x7f) { + if (byte < 0x20u || byte == 0x7fu) { return false; } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index edbe9d8..52105d1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,6 +19,43 @@ cpp_request_add_test(cpp_request_tests ${CMAKE_CURRENT_SOURCE_DIR}/tcp_io_test.cpp ) +set(CPP_REQUEST_PUBLIC_HEADERS + client.hpp + error.hpp + headers.hpp + request.hpp + response.hpp + response_limits.hpp + result.hpp + url.hpp +) + +set(CPP_REQUEST_PUBLIC_HEADER_SOURCES) +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/public_headers") + +foreach(header IN LISTS CPP_REQUEST_PUBLIC_HEADERS) + string(REPLACE ".hpp" "" header_stem "${header}") + set( + source + "${CMAKE_CURRENT_BINARY_DIR}/public_headers/${header_stem}.cpp" + ) + file(WRITE "${source}" "#include \n") + list(APPEND CPP_REQUEST_PUBLIC_HEADER_SOURCES "${source}") +endforeach() + +add_library( + cpp_request_public_headers_compile + OBJECT + ${CPP_REQUEST_PUBLIC_HEADER_SOURCES} +) +target_include_directories( + cpp_request_public_headers_compile + PRIVATE + ${PROJECT_SOURCE_DIR}/include +) +target_compile_features(cpp_request_public_headers_compile PRIVATE cxx_std_17) +cpp_request_apply_warnings(cpp_request_public_headers_compile) + add_test( NAME PackageConsumer.InstallAndUse COMMAND diff --git a/tests/package_consumer/main.cpp b/tests/package_consumer/main.cpp index f406adb..04b34f1 100644 --- a/tests/package_consumer/main.cpp +++ b/tests/package_consumer/main.cpp @@ -1,12 +1,17 @@ #include #include +#include #include +#include #include +#include +#include int main() { cpp_request::Request request{ cpp_request::Method::Get, "http://example.com/"}; + request.headers().add("Accept", "text/plain"); request.add_query_param("q", "cpp request"); cpp_request::Client client; @@ -14,6 +19,16 @@ int main() { limits.max_body_bytes = 1024; client.set_response_limits(limits); + const auto parsed = cpp_request::Url::parse("http://example.com/path"); + if (!parsed) { + return 1; + } + + auto result = cpp_request::Result::success(42); + if (!result || result.value() != 42) { + return 1; + } + return cpp_request::error_message(cpp_request::ErrorCode::InvalidUrl).empty() ? 1 : 0;