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
14 changes: 12 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,22 @@ jobs:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Setup vcpkg
uses: lukka/run-vcpkg@v11

- name: Configure CMake
shell: bash
run: |
cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake -B build \
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"

- name: Build Project
shell: bash
run: |
cmake --build build --config ${{ matrix.build_type }}
cmake --build build --config ${{ matrix.build_type }}

- name: Run Tests
shell: bash
run: |
ctest --test-dir build --build-config ${{ matrix.build_type }} --output-on-failure
7 changes: 5 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ project(
LANGUAGES CXX C
)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

include(CTest)
include(cmake/modules/output.cmake)
include(cmake/modules/warning.cmake)
include(cmake/modules/sanitizing.cmake)
include(cmake/modules/optimizing.cmake)

add_subdirectory(${CMAKE_SOURCE_DIR}/src)
add_subdirectory(${CMAKE_SOURCE_DIR}/tests)
if(BUILD_TESTING)
add_subdirectory(${CMAKE_SOURCE_DIR}/tests)
endif()
add_subdirectory(${CMAKE_SOURCE_DIR}/benchmarks)
add_subdirectory(${CMAKE_SOURCE_DIR}/examples)
5 changes: 2 additions & 3 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@
"cacheVariables": {
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++",
"CMAKE_CXX_STANDARD": "23",
"CMAKE_CXX_STANDARD": "17",
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"CMAKE_CXX_EXTENSIONS": "OFF",
"CMAKE_CXX_FLAGS": "-Wno-c2y-extensions",
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
}, {
Expand Down Expand Up @@ -40,4 +39,4 @@
"configurePreset": "release"
}
]
}
}
73 changes: 73 additions & 0 deletions include/cpp_request/error.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#pragma once

#include <string_view>

namespace cpp_request {

enum class ErrorCode {
InvalidUrl,
UnsupportedScheme,
InvalidPort,

ResolveFailed,
SocketCreateFailed,
ConnectFailed,
ConnectTimeout,
WriteFailed,
WriteTimeout,
ReadFailed,
ReadTimeout,
ConnectionClosed,

MalformedResponse,
InvalidStatusLine,
InvalidHeader,
InvalidContentLength,
InvalidChunkSize,
InvalidChunkFraming,
ConflictingMessageFraming,
UnexpectedEof,

RedirectLimitExceeded,
MissingRedirectLocation,
UnsupportedRedirectScheme,

Unknown
};

struct Error {
ErrorCode code;
int native_code{0};
};

[[nodiscard]] constexpr std::string_view error_message(ErrorCode code) noexcept {
switch (code) {
case ErrorCode::InvalidUrl: return "invalid URL";
case ErrorCode::UnsupportedScheme: return "unsupported URL scheme";
case ErrorCode::InvalidPort: return "invalid port";
case ErrorCode::ResolveFailed: return "name resolution failed";
case ErrorCode::SocketCreateFailed: return "socket creation failed";
case ErrorCode::ConnectFailed: return "connection failed";
case ErrorCode::ConnectTimeout: return "connection timed out";
case ErrorCode::WriteFailed: return "write failed";
case ErrorCode::WriteTimeout: return "write timed out";
case ErrorCode::ReadFailed: return "read failed";
case ErrorCode::ReadTimeout: return "read timed out";
case ErrorCode::ConnectionClosed: return "connection closed";
case ErrorCode::MalformedResponse: return "malformed HTTP response";
case ErrorCode::InvalidStatusLine: return "invalid HTTP status line";
case ErrorCode::InvalidHeader: return "invalid HTTP header";
case ErrorCode::InvalidContentLength: return "invalid Content-Length";
case ErrorCode::InvalidChunkSize: return "invalid chunk size";
case ErrorCode::InvalidChunkFraming: return "invalid chunk framing";
case ErrorCode::ConflictingMessageFraming: return "conflicting HTTP message framing";
case ErrorCode::UnexpectedEof: return "unexpected end of stream";
case ErrorCode::RedirectLimitExceeded: return "redirect limit exceeded";
case ErrorCode::MissingRedirectLocation: return "redirect location missing";
case ErrorCode::UnsupportedRedirectScheme: return "unsupported redirect scheme";
case ErrorCode::Unknown: return "unknown error";
}
return "unknown error";
}

} // namespace cpp_request
58 changes: 58 additions & 0 deletions include/cpp_request/result.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#pragma once

#include <cassert>
#include <type_traits>
#include <utility>
#include <variant>

#include <cpp_request/error.hpp>

namespace cpp_request {

template <typename T>
class [[nodiscard]] Result {
public:
Result(T value)
: storage_(std::move(value)) {}

Result(Error error)
: storage_(error) {}

[[nodiscard]] bool has_value() const noexcept {
return std::holds_alternative<T>(storage_);
}

[[nodiscard]] explicit operator bool() const noexcept {
return has_value();
}

T& value() & noexcept {
assert(has_value());
return std::get<T>(storage_);
}

const T& value() const& noexcept {
assert(has_value());
return std::get<T>(storage_);
}

T&& value() && noexcept {
assert(has_value());
return std::get<T>(std::move(storage_));
}

Error& error() & noexcept {
assert(!has_value());
return std::get<Error>(storage_);
}

const Error& error() const& noexcept {
assert(!has_value());
return std::get<Error>(storage_);
}

private:
std::variant<T, Error> storage_;
};

} // namespace cpp_request
45 changes: 45 additions & 0 deletions include/cpp_request/url.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#pragma once

#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>

#include <cpp_request/result.hpp>

namespace cpp_request {

class Url {
public:
[[nodiscard]] static Result<Url> parse(std::string_view input);

[[nodiscard]] std::string_view scheme() const noexcept;
[[nodiscard]] std::string_view host() const noexcept;
[[nodiscard]] std::string_view path() const noexcept;
[[nodiscard]] std::string_view query() const noexcept;
[[nodiscard]] std::string_view target() const noexcept;

[[nodiscard]] std::uint16_t port() const noexcept { return port_; }
[[nodiscard]] bool has_explicit_port() const noexcept { return has_explicit_port_; }
[[nodiscard]] bool host_is_ipv6_literal() const noexcept { return host_is_ipv6_literal_; }

private:
std::string storage_;

std::size_t scheme_begin_{0};
std::size_t scheme_size_{0};
std::size_t host_begin_{0};
std::size_t host_size_{0};
std::size_t path_begin_{0};
std::size_t path_size_{0};
std::size_t query_begin_{0};
std::size_t query_size_{0};
std::size_t target_begin_{0};
std::size_t target_size_{0};

std::uint16_t port_{80};
bool has_explicit_port_{false};
bool host_is_ipv6_literal_{false};
};

} // namespace cpp_request
7 changes: 5 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
add_library(internal
add_library(internal
${CMAKE_CURRENT_SOURCE_DIR}/dummy.cpp
${CMAKE_CURRENT_SOURCE_DIR}/url.cpp
)

add_library(${PROJECT_NAME}::internal ALIAS internal)
Expand All @@ -9,4 +10,6 @@ target_include_directories(internal
${CMAKE_SOURCE_DIR}/include
PRIVATE
${CMAKE_SOURCE_DIR}/src
)
)

target_compile_features(internal PUBLIC cxx_std_17)
Loading
Loading