Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.21)

project(
cpp_request
VERSION 0.9.0
VERSION 1.0.0
Comment thread
zuudevs marked this conversation as resolved.
DESCRIPTION "A lightweight synchronous C++17 HTTP/1.1 client built on native sockets"
LANGUAGES CXX
)
Expand Down
2 changes: 1 addition & 1 deletion cmake/modules/install.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function(cpp_request_configure_install)
write_basic_package_version_file(
"${PROJECT_BINARY_DIR}/cpp_requestConfigVersion.cmake"
VERSION "${PROJECT_VERSION}"
COMPATIBILITY SameMinorVersion
COMPATIBILITY SameMajorVersion
)

install(
Expand Down
26 changes: 13 additions & 13 deletions docs/api/error-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

- Project: `cpp_request`
- Target release: MVP v1.0
- Status: Proposed v1 contract
- Status: Frozen v1.0 contract
- Language baseline: C++17

## Goals

The v1 error model is designed to be:
The v1 error model is:

1. **Structured** — callers branch on stable library-owned codes, not diagnostic strings.
2. **Non-exception-based** — expected URL, DNS, socket, timeout, and protocol failures are returned through `Result<T>`.
Expand All @@ -19,7 +19,7 @@ The v1 error model is designed to be:

## Public Error Shape

The public contract should conceptually expose:
The stable public contract is:

```cpp
enum class ErrorCode {
Expand Down Expand Up @@ -60,7 +60,7 @@ struct Error {
};
```

The exact physical representation may change during implementation, but the semantic contract above is frozen for v1 unless implementation proves a concrete correctness issue.
The physical representation may evolve internally, but the semantic contract above is frozen for the v1 stable line unless a correctness fix requires a compatible change.

## `native_code`

Expand All @@ -72,7 +72,7 @@ Rules:
- POSIX errors may preserve `errno`.
- Windows errors may preserve the result of `WSAGetLastError()` or an equivalent native code.
- callers must not use `native_code` as the primary portable error contract.
- library behavior must branch on `ErrorCode`, not platform-native numbers.
- library behavior branches on `ErrorCode`, not platform-native numbers.

## Error Categories

Expand Down Expand Up @@ -124,7 +124,7 @@ Rules:

### Fallback

`Unknown` is reserved for failures that cannot yet be classified safely. New implementation paths should prefer a specific stable code whenever practical.
`Unknown` is reserved for failures that cannot be classified safely. New implementation paths should prefer a specific stable code whenever practical.

## HTTP Status Codes Are Not Library Errors

Expand All @@ -138,7 +138,7 @@ flowchart LR
D --> E[Response.status_code may be 2xx, 4xx, 5xx, etc.]
```

The library must not convert HTTP status codes into `ErrorCode` values automatically.
The library does not convert HTTP status codes into `ErrorCode` values automatically.

This separation allows callers to distinguish:

Expand All @@ -158,8 +158,8 @@ flowchart TD
Rules:

- platform-specific numeric values never replace `ErrorCode`.
- lower-layer failures should not be reclassified unless the upper layer has additional semantic information.
- errors must preserve the most specific meaningful portable classification available.
- lower-layer failures are not reclassified unless the upper layer has additional semantic information.
- errors preserve the most specific meaningful portable classification available.

## Timeout Classification

Expand All @@ -169,7 +169,7 @@ Timeouts remain distinct by operation:
- `ReadTimeout`
- `WriteTimeout`

They must not collapse into one generic timeout code in v1 because callers may need different recovery/logging behavior for each stage.
They do not collapse into one generic timeout code in v1 because callers may need different recovery/logging behavior for each stage.

## EOF Semantics

Expand All @@ -195,7 +195,7 @@ Exceeding a configured limit:

## Diagnostic Message Function

The library may expose a lightweight function such as:
The library exposes a lightweight function:

```cpp
std::string_view error_message(ErrorCode code) noexcept;
Expand All @@ -210,6 +210,6 @@ Requirements:

## Exception Policy

Expected runtime failures represented by this document must not require exceptions.
Expected runtime failures represented by this document do not require exceptions.

The v1 contract does not require a global `noexcept` guarantee for every public function, because standard-library allocation may still fail. However, network/protocol error reporting itself must use `Result<T>` rather than throwing library-specific exceptions.
The v1 contract does not provide a global `noexcept` guarantee for every public function, because standard-library allocation may still fail. Network/protocol error reporting itself uses `Result<T>` rather than library-specific exceptions.
55 changes: 22 additions & 33 deletions docs/api/lifetime.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

- Project: `cpp_request`
- Target release: MVP v1.0
- Status: Proposed API lifetime baseline
- Status: Frozen v1.0 contract
- Language baseline: C++17

This document defines ownership and lifetime rules for public API data, especially where `std::string_view` is used.
This document defines the stable ownership and lifetime rules for public API data, especially where `std::string_view` is used.

---

Expand Down Expand Up @@ -45,14 +45,9 @@ cpp_request::Request req{
};
```

If `Request` stores the supplied view directly, the temporary string is destroyed immediately and the view dangles.
`Request` stores the supplied view directly, so a temporary string would be destroyed immediately and leave the view dangling.

Therefore, either:

- callers keep borrowed storage alive, or
- implementation/API overloads explicitly copy when ownership is requested.

The default v1 model should prefer borrowing rather than implicit allocation.
Callers must keep borrowed URL storage alive while the `Request` may read it.

### Request body

Expand All @@ -62,17 +57,15 @@ Because v1 request execution is synchronous, the library does not retain the bod

### Request headers

Header storage strategy may be owned internally by `Headers` to avoid fragile user-side lifetime requirements for individually inserted fields.

The public contract should prefer safety here: after `Headers::add()` or `Headers::set()` returns successfully, the caller should not be required to keep the source header strings alive.
`Headers` owns inserted header names and values. After `Headers::add()` or `Headers::set()` returns successfully, the caller does not need to keep the source header strings alive.

This allows temporary values to be used safely when constructing headers while keeping `std::string_view` primarily as an input parameter optimization.

---

## Response-Side Lifetime

`Response` owns its completed response body and any storage required to expose stable response metadata.
`Response` owns its completed response body and the storage required to expose stable response metadata.

A `std::string_view` returned from a `Response` accessor remains valid only while:

Expand All @@ -97,9 +90,7 @@ Views into a destroyed response are invalid.

## `Url` Lifetime

A parsed `Url` should expose stable component views for the lifetime of the `Url` object.

Therefore, if parsing requires normalized or reconstructed storage, `Url` should own that storage internally rather than exposing views into temporary parser buffers.
A parsed `Url` owns normalized URL storage internally and exposes stable component views for the lifetime of the `Url` object.

The public contract is:

Expand All @@ -114,13 +105,13 @@ The public contract is:

Views returned from header lookup remain valid until the corresponding `Headers` object is mutated in a way that can invalidate internal storage or until it is destroyed.

The implementation should document iterator/reference invalidation rules once the final container representation is selected.
Iteration/reference invalidation follows the owning container's mutation behavior and should be treated conservatively by callers.

---

## Move Semantics

Resource-owning or storage-owning public types should support efficient move semantics where appropriate.
Resource-owning or storage-owning public types support efficient move semantics where appropriate.

After moving from an object:

Expand All @@ -137,21 +128,19 @@ This keeps the contract simple and avoids binding the API to container-specific

## Copy Semantics

Value-oriented types that own only ordinary data may be copyable where useful.

Native socket ownership must never be duplicated by copy.
Value-oriented types that own ordinary data are copyable where their declarations allow it.

`Client` copyability is not guaranteed in v1 because it may own reusable transport state.
Native socket ownership is never duplicated by copy.

The expected safe default is:
The stable v1 rules are:

- `Client`: non-copyable, movable if practical,
- internal socket owners: non-copyable, movable,
- `Response`: movable and copyability may be implementation-dependent,
- `Headers`: regular owning value type where practical,
- `Url`: regular owning value type where practical.
- `Client`: non-copyable and movable,
- internal socket owners: non-copyable and movable,
- `Response`: ordinary owning value type according to its generated special members,
- `Headers`: owning value type,
- `Url`: owning value type.

Exact special-member declarations are finalized during implementation/public-header review.
The declarations in the installed public headers are authoritative for exact special-member availability.

---

Expand All @@ -174,13 +163,13 @@ sequenceDiagram
Note over App,Req: Borrowed input no longer needed by execution
```

The library must not retain request-side borrowed views for asynchronous work after the synchronous call returns.
The library does not retain request-side borrowed views for asynchronous work after the synchronous call returns.

---

## Invalid Lifetime Patterns

The following are explicitly unsafe unless an owning overload copies the data:
The following are explicitly unsafe:

- constructing a stored `Request` view from a temporary `std::string`,
- storing a response-derived view after destroying the `Response`,
Expand All @@ -191,10 +180,10 @@ The following are explicitly unsafe unless an owning overload copies the data:

## Design Rationale

The v1 lifetime model intentionally balances performance and development complexity:
The v1 lifetime model intentionally balances performance and implementation complexity:

- `std::string_view` avoids unnecessary copies for request inputs,
- synchronous execution bounds how long borrowed request data is needed,
- response data remains owned for safe user access,
- ownership-heavy structures such as headers can copy at construction time where lifetime safety is more valuable than micro-optimizing tiny strings,
- ownership-heavy structures such as headers copy at construction time where lifetime safety is more valuable than micro-optimizing tiny strings,
- no custom string-view implementation is required because C++17 is the project baseline.
Loading
Loading