diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index a7d2ac2..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,9 +0,0 @@
-[submodule "Vendor/mimalloc"]
- path = Vendor/mimalloc
- url = https://github.com/microsoft/mimalloc
-[submodule "Vendor/expected"]
- path = Vendor/expected
- url = https://github.com/TartanLlama/expected
-[submodule "Vendor/unordered_dense"]
- path = Vendor/unordered_dense
- url = https://github.com/martinus/unordered_dense
diff --git a/CMake/FindDeps.cmake b/CMake/FindDeps.cmake
new file mode 100644
index 0000000..d9347b4
--- /dev/null
+++ b/CMake/FindDeps.cmake
@@ -0,0 +1,127 @@
+include(FetchContent)
+
+FetchContent_Declare(
+ httplib
+ GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git
+ GIT_TAG v0.14.3
+ SYSTEM
+ EXCLUDE_FROM_ALL
+ OVERRIDE_FIND_PACKAGE
+)
+
+FetchContent_Declare(
+ OpenSSL
+ GIT_REPOSITORY https://github.com/janbar/openssl-cmake.git
+ GIT_TAG master
+ SYSTEM
+ EXCLUDE_FROM_ALL
+ OVERRIDE_FIND_PACKAGE
+)
+
+FetchContent_Declare(
+ nlohmann_json
+ GIT_REPOSITORY https://github.com/nlohmann/json.git
+ GIT_TAG v3.11.3
+ SYSTEM
+ EXCLUDE_FROM_ALL
+ OVERRIDE_FIND_PACKAGE
+)
+
+FetchContent_Declare(
+ glaze
+ GIT_REPOSITORY https://github.com/stephenberry/glaze.git
+ GIT_TAG v4.3.1
+ SYSTEM
+ EXCLUDE_FROM_ALL
+ OVERRIDE_FIND_PACKAGE
+)
+
+FetchContent_Declare(
+ simdjson
+ GIT_REPOSITORY https://github.com/simdjson/simdjson.git
+ GIT_TAG v3.11.0
+ SYSTEM
+ EXCLUDE_FROM_ALL
+ OVERRIDE_FIND_PACKAGE
+)
+
+FetchContent_Declare(
+ ZLIB
+ GIT_REPOSITORY https://github.com/madler/zlib.git
+ GIT_TAG v1.3.1
+ SYSTEM
+ EXCLUDE_FROM_ALL
+ OVERRIDE_FIND_PACKAGE
+)
+
+FetchContent_Declare(
+ zstd
+ GIT_REPOSITORY https://github.com/facebook/zstd.git
+ GIT_TAG v1.5.6
+ SOURCE_SUBDIR build/cmake
+ SYSTEM
+ EXCLUDE_FROM_ALL
+ OVERRIDE_FIND_PACKAGE
+)
+
+FetchContent_Declare(
+ mimalloc
+ GIT_REPOSITORY https://github.com/microsoft/mimalloc.git
+ GIT_TAG v2.1.7
+ SYSTEM
+ EXCLUDE_FROM_ALL
+ OVERRIDE_FIND_PACKAGE
+)
+
+FetchContent_Declare(
+ tl-expected
+ GIT_REPOSITORY https://github.com/TartanLlama/expected.git
+ GIT_TAG v1.1.0
+ SYSTEM
+ EXCLUDE_FROM_ALL
+ OVERRIDE_FIND_PACKAGE
+)
+
+FetchContent_Declare(
+ unordered_dense
+ GIT_REPOSITORY https://github.com/martinus/unordered_dense.git
+ GIT_TAG v4.4.0
+ SYSTEM
+ EXCLUDE_FROM_ALL
+ OVERRIDE_FIND_PACKAGE
+)
+
+find_package(ZLIB REQUIRED)
+if(TARGET zlibstatic AND NOT TARGET ZLIB::ZLIB)
+ add_library(ZLIB::ZLIB ALIAS zlibstatic)
+elseif(TARGET zlib AND NOT TARGET ZLIB::ZLIB)
+ add_library(ZLIB::ZLIB ALIAS zlib)
+endif()
+
+find_package(zstd REQUIRED)
+find_package(glaze REQUIRED)
+find_package(simdjson REQUIRED)
+find_package(nlohmann_json REQUIRED)
+find_package(unordered_dense REQUIRED)
+
+find_package(OpenSSL REQUIRED)
+if(TARGET ssl AND NOT TARGET OpenSSL::SSL)
+ add_library(OpenSSL::SSL ALIAS ssl)
+ message(STATUS "Patched OpenSSL::SSL alias for Curl")
+endif()
+if(TARGET crypto AND NOT TARGET OpenSSL::Crypto)
+ add_library(OpenSSL::Crypto ALIAS crypto)
+ message(STATUS "Patched OpenSSL::Crypto alias for Curl")
+endif()
+
+set(MI_BUILD_SHARED ON CACHE BOOL "" FORCE)
+set(MI_BUILD_STATIC ON CACHE BOOL "" FORCE)
+set(MI_BUILD_TESTS OFF CACHE BOOL "" FORCE)
+find_package(mimalloc REQUIRED)
+
+set(EXPECTED_BUILD_TESTS OFF CACHE BOOL "" FORCE)
+find_package(tl-expected REQUIRED)
+
+set(HTTPLIB_REQUIRE_OPENSSL ON CACHE BOOL "" FORCE)
+set(HTTPLIB_REQUIRE_ZLIB ON CACHE BOOL "" FORCE)
+find_package(httplib REQUIRED)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 01e92d4..9e2d8c7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,6 +13,8 @@ project(IACore)
enable_language(C)
+include(CMake/FindDeps.cmake)
+
# Default to ON if root, OFF if dependency
option(IACore_BUILD_TESTS "Build unit tests" ${PROJECT_IS_TOP_LEVEL})
@@ -54,10 +56,6 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU"
)
endif()
-add_definitions(-D_CRT_SECURE_NO_WARNINGS)
-
-add_subdirectory(Vendor/)
-
add_subdirectory(Src/)
if(IACore_BUILD_TESTS)
diff --git a/Src/IACore/CMakeLists.txt b/Src/IACore/CMakeLists.txt
index fa687b2..dde91b4 100644
--- a/Src/IACore/CMakeLists.txt
+++ b/Src/IACore/CMakeLists.txt
@@ -1,11 +1,14 @@
set(SRC_FILES
+ "imp/cpp/JSON.cpp"
"imp/cpp/IACore.cpp"
"imp/cpp/Logger.cpp"
"imp/cpp/FileOps.cpp"
"imp/cpp/AsyncOps.cpp"
+ "imp/cpp/DataOps.cpp"
"imp/cpp/SocketOps.cpp"
"imp/cpp/StringOps.cpp"
"imp/cpp/ProcessOps.cpp"
+ "imp/cpp/HttpClient.cpp"
"imp/cpp/StreamReader.cpp"
"imp/cpp/StreamWriter.cpp"
)
@@ -15,7 +18,20 @@ add_library(IACore STATIC ${SRC_FILES})
target_include_directories(IACore PUBLIC inc/)
target_include_directories(IACore PRIVATE imp/hpp/)
-target_link_libraries(IACore PUBLIC tl::expected unordered_dense::unordered_dense)
+target_link_libraries(IACore PUBLIC
+ libzstd_static
+ ZLIB::ZLIB
+ tl::expected
+ glaze::glaze
+ simdjson::simdjson
+ nlohmann_json::nlohmann_json
+ unordered_dense::unordered_dense
+)
+
+target_link_libraries(IACore PRIVATE
+ httplib::httplib
+ OpenSSL::SSL
+)
if(WIN32)
target_link_libraries(IACore PUBLIC mimalloc-static)
@@ -34,4 +50,9 @@ target_compile_options(IACore INTERFACE
define_property(TARGET PROPERTY USE_EXCEPTIONS
BRIEF_DOCS "If ON, this target is allowed to use C++ exceptions."
FULL_DOCS "Prevents IACore from propagating -fno-exceptions to this target."
-)
\ No newline at end of file
+)
+
+target_compile_definitions(IACore PRIVATE
+ CPPHTTPLIB_OPENSSL_SUPPORT
+ CPPHTTPLIB_ZLIB_SUPPORT
+)
diff --git a/Src/IACore/imp/cpp/DataOps.cpp b/Src/IACore/imp/cpp/DataOps.cpp
new file mode 100644
index 0000000..30f43e5
--- /dev/null
+++ b/Src/IACore/imp/cpp/DataOps.cpp
@@ -0,0 +1,168 @@
+// IACore-OSS; The Core Library for All IA Open Source Projects
+// Copyright (C) 2025 IAS (ias@iasoft.dev)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include
+
+#include
+#include
+
+namespace IACore
+{
+ Expected, String> DataOps::ZlibInflate(IN Span data)
+ {
+ z_stream zs{};
+ zs.zalloc = Z_NULL;
+ zs.zfree = Z_NULL;
+ zs.opaque = Z_NULL;
+
+ if (inflateInit2(&zs, 15 + 32) != Z_OK)
+ return MakeUnexpected("Failed to initialize zlib inflate");
+
+ zs.next_in = const_cast(data.data());
+ zs.avail_in = static_cast(data.size());
+
+ Vector outBuffer;
+ outBuffer.resize(data.size() * 2);
+
+ int ret;
+ do
+ {
+ if (zs.total_out >= outBuffer.size())
+ {
+ outBuffer.resize(outBuffer.size() * 2);
+ }
+
+ zs.next_out = reinterpret_cast(outBuffer.data() + zs.total_out);
+ zs.avail_out = static_cast(outBuffer.size() - zs.total_out);
+
+ ret = inflate(&zs, Z_NO_FLUSH);
+
+ } while (ret == Z_OK);
+
+ inflateEnd(&zs);
+
+ if (ret != Z_STREAM_END)
+ return MakeUnexpected("Failed to inflate, corrupt or incomplete data");
+
+ outBuffer.resize(zs.total_out);
+ return outBuffer;
+ }
+
+ Expected, String> DataOps::ZlibDeflate(IN Span data)
+ {
+ z_stream zs{};
+ zs.zalloc = Z_NULL;
+ zs.zfree = Z_NULL;
+ zs.opaque = Z_NULL;
+
+ if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK)
+ return MakeUnexpected("Failed to initialize zlib deflate");
+
+ zs.next_in = const_cast(data.data());
+ zs.avail_in = static_cast(data.size());
+
+ Vector outBuffer;
+
+ outBuffer.resize(deflateBound(&zs, data.size()));
+
+ zs.next_out = reinterpret_cast(outBuffer.data());
+ zs.avail_out = static_cast(outBuffer.size());
+
+ int ret = deflate(&zs, Z_FINISH);
+
+ if (ret != Z_STREAM_END)
+ {
+ deflateEnd(&zs);
+ return MakeUnexpected("Failed to deflate, ran out of buffer memory");
+ }
+
+ outBuffer.resize(zs.total_out);
+
+ deflateEnd(&zs);
+ return outBuffer;
+ }
+
+ Expected, String> DataOps::ZstdInflate(IN Span data)
+ {
+ unsigned long long const contentSize = ZSTD_getFrameContentSize(data.data(), data.size());
+
+ if (contentSize == ZSTD_CONTENTSIZE_ERROR)
+ return MakeUnexpected("Failed to inflate: Not valid ZSTD compressed data");
+
+ if (contentSize != ZSTD_CONTENTSIZE_UNKNOWN)
+ {
+ // FAST PATH: We know the size
+ Vector outBuffer;
+ outBuffer.resize(static_cast(contentSize));
+
+ size_t const dSize = ZSTD_decompress(outBuffer.data(), outBuffer.size(), data.data(), data.size());
+
+ if (ZSTD_isError(dSize))
+ return MakeUnexpected(std::format("Failed to inflate: {}", ZSTD_getErrorName(dSize)));
+
+ return outBuffer;
+ }
+
+ ZSTD_DCtx *dctx = ZSTD_createDCtx();
+ Vector outBuffer;
+ outBuffer.resize(data.size() * 2);
+
+ ZSTD_inBuffer input = {data.data(), data.size(), 0};
+ ZSTD_outBuffer output = {outBuffer.data(), outBuffer.size(), 0};
+
+ size_t ret;
+ do
+ {
+ ret = ZSTD_decompressStream(dctx, &output, &input);
+
+ if (ZSTD_isError(ret))
+ {
+ ZSTD_freeDCtx(dctx);
+ return MakeUnexpected(std::format("Failed to inflate: {}", ZSTD_getErrorName(ret)));
+ }
+
+ if (output.pos == output.size)
+ {
+ size_t newSize = outBuffer.size() * 2;
+ outBuffer.resize(newSize);
+ output.dst = outBuffer.data();
+ output.size = newSize;
+ }
+
+ } while (ret != 0);
+
+ outBuffer.resize(output.pos);
+ ZSTD_freeDCtx(dctx);
+
+ return outBuffer;
+ }
+
+ Expected, String> DataOps::ZstdDeflate(IN Span data)
+ {
+ size_t const maxDstSize = ZSTD_compressBound(data.size());
+
+ Vector outBuffer;
+ outBuffer.resize(maxDstSize);
+
+ size_t const compressedSize = ZSTD_compress(outBuffer.data(), maxDstSize, data.data(), data.size(), 3);
+
+ if (ZSTD_isError(compressedSize))
+ return MakeUnexpected(std::format("Failed to deflate: {}", ZSTD_getErrorName(compressedSize)));
+
+ outBuffer.resize(compressedSize);
+ return outBuffer;
+ }
+} // namespace IACore
\ No newline at end of file
diff --git a/Src/IACore/imp/cpp/HttpClient.cpp b/Src/IACore/imp/cpp/HttpClient.cpp
new file mode 100644
index 0000000..0258f18
--- /dev/null
+++ b/Src/IACore/imp/cpp/HttpClient.cpp
@@ -0,0 +1,205 @@
+// IACore-OSS; The Core Library for All IA Open Source Projects
+// Copyright (C) 2025 IAS (ias@iasoft.dev)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include
+
+#include
+
+namespace IACore
+{
+ httplib::Headers BuildHeaders(IN Span headers, IN PCCHAR defaultContentType)
+ {
+ httplib::Headers out;
+ bool hasContentType = false;
+
+ for (const auto &h : headers)
+ {
+ std::string key = HttpClient::HeaderTypeToString(h.first); // Your existing helper
+ out.emplace(key, h.second);
+
+ if (h.first == HttpClient::EHeaderType::CONTENT_TYPE)
+ hasContentType = true;
+ }
+
+ if (!hasContentType && defaultContentType)
+ out.emplace("Content-Type", defaultContentType);
+ return out;
+ }
+
+ HttpClient::HttpClient(IN CONST String &host)
+ : m_client(new httplib::Client(host)), m_lastResponseCode(EResponseCode::INTERNAL_SERVER_ERROR)
+ {
+ }
+
+ HttpClient::~HttpClient()
+ {
+ }
+
+ Expected HttpClient::RawGet(IN CONST String &path, IN Span headers,
+ IN PCCHAR defaultContentType)
+ {
+ auto httpHeaders = BuildHeaders(headers, defaultContentType);
+
+ static_cast(m_client)->enable_server_certificate_verification(false);
+ auto res = static_cast(m_client)->Get(path.c_str(), httpHeaders);
+
+ if (res)
+ {
+ m_lastResponseCode = static_cast(res->status);
+ if (res->status >= 200 && res->status < 300)
+ return res->body;
+ else
+ return MakeUnexpected(std::format("HTTP Error {}", res->status));
+ }
+
+ return MakeUnexpected(std::format("Network Error: {}", httplib::to_string(res.error())));
+ }
+
+ Expected HttpClient::RawPost(IN CONST String &path, IN Span headers,
+ IN CONST String &body, IN PCCHAR defaultContentType)
+ {
+ auto httpHeaders = BuildHeaders(headers, defaultContentType);
+
+ String contentType = defaultContentType;
+ if (httpHeaders.count("Content-Type"))
+ {
+ contentType = httpHeaders.find("Content-Type")->second;
+ }
+
+ static_cast(m_client)->enable_server_certificate_verification(false);
+ auto res = static_cast(m_client)->Post(path.c_str(), httpHeaders, body, contentType.c_str());
+
+ if (res)
+ {
+ m_lastResponseCode = static_cast(res->status);
+ if (res->status >= 200 && res->status < 300)
+ return res->body;
+ else
+ return MakeUnexpected(std::format("HTTP Error {}", res->status));
+ }
+
+ return MakeUnexpected(std::format("Network Error: {}", httplib::to_string(res.error())));
+ }
+} // namespace IACore
+
+namespace IACore
+{
+ HttpClient::Header HttpClient::CreateHeader(IN EHeaderType key, IN CONST String &value)
+ {
+ return std::make_pair(key, value);
+ }
+
+ String HttpClient::UrlEncode(IN CONST String &value)
+ {
+ std::stringstream escaped;
+ escaped.fill('0');
+ escaped << std::hex << std::uppercase;
+
+ for (char c : value)
+ {
+ if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || c == '.' || c == '~')
+ escaped << c;
+ else
+ escaped << '%' << std::setw(2) << static_cast(static_cast(c));
+ }
+
+ return escaped.str();
+ }
+
+ String HttpClient::UrlDecode(IN CONST String &value)
+ {
+ String result;
+ result.reserve(value.length());
+
+ for (size_t i = 0; i < value.length(); ++i)
+ {
+ if (value[i] == '%' && i + 2 < value.length())
+ {
+ std::string hexStr = value.substr(i + 1, 2);
+ char decodedChar = static_cast(std::strtol(hexStr.c_str(), nullptr, 16));
+ result += decodedChar;
+ i += 2;
+ }
+ else if (value[i] == '+')
+ result += ' ';
+ else
+ result += value[i];
+ }
+
+ return result;
+ }
+
+ String HttpClient::HeaderTypeToString(IN EHeaderType type)
+ {
+ switch (type)
+ {
+ case EHeaderType::ACCEPT:
+ return "Accept";
+ case EHeaderType::ACCEPT_CHARSET:
+ return "Accept-Charset";
+ case EHeaderType::ACCEPT_ENCODING:
+ return "Accept-Encoding";
+ case EHeaderType::ACCEPT_LANGUAGE:
+ return "Accept-Language";
+ case EHeaderType::AUTHORIZATION:
+ return "Authorization";
+ case EHeaderType::CACHE_CONTROL:
+ return "Cache-Control";
+ case EHeaderType::CONNECTION:
+ return "Connection";
+ case EHeaderType::CONTENT_LENGTH:
+ return "Content-Length";
+ case EHeaderType::CONTENT_TYPE:
+ return "Content-Type";
+ case EHeaderType::COOKIE:
+ return "Cookie";
+ case EHeaderType::DATE:
+ return "Date";
+ case EHeaderType::EXPECT:
+ return "Expect";
+ case EHeaderType::HOST:
+ return "Host";
+ case EHeaderType::IF_MATCH:
+ return "If-Match";
+ case EHeaderType::IF_MODIFIED_SINCE:
+ return "If-Modified-Since";
+ case EHeaderType::IF_NONE_MATCH:
+ return "If-None-Match";
+ case EHeaderType::ORIGIN:
+ return "Origin";
+ case EHeaderType::PRAGMA:
+ return "Pragma";
+ case EHeaderType::PROXY_AUTHORIZATION:
+ return "Proxy-Authorization";
+ case EHeaderType::RANGE:
+ return "Range";
+ case EHeaderType::REFERER:
+ return "Referer";
+ case EHeaderType::TE:
+ return "TE";
+ case EHeaderType::UPGRADE:
+ return "Upgrade";
+ case EHeaderType::USER_AGENT:
+ return "User-Agent";
+ case EHeaderType::VIA:
+ return "Via";
+ case EHeaderType::WARNING:
+ return "Warning";
+ default:
+ return "";
+ }
+ }
+} // namespace IACore
\ No newline at end of file
diff --git a/Src/IACore/imp/cpp/JSON.cpp b/Src/IACore/imp/cpp/JSON.cpp
new file mode 100644
index 0000000..0f63f06
--- /dev/null
+++ b/Src/IACore/imp/cpp/JSON.cpp
@@ -0,0 +1,43 @@
+// IACore-OSS; The Core Library for All IA Open Source Projects
+// Copyright (C) 2025 IAS (ias@iasoft.dev)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include
+
+namespace IACore
+{
+ Expected JSON::Parse(IN CONST String &json)
+ {
+ const auto parseResult = nlohmann::json::parse(json, nullptr, false, true);
+ if (parseResult.is_discarded())
+ return MakeUnexpected("Failed to parse JSON");
+ return parseResult;
+ }
+
+ Expected JSON::ParseReadOnly(IN CONST String &json)
+ {
+ simdjson::error_code error{};
+ simdjson::dom::parser parser;
+ simdjson::dom::object object;
+ if ((error = parser.parse(json).get(object)))
+ return MakeUnexpected(std::format("Failed to parse JSON : {}", simdjson::error_message(error)));
+ return object;
+ }
+
+ String JSON::Encode(IN nlohmann::json data)
+ {
+ return data.dump();
+ }
+} // namespace IACore
\ No newline at end of file
diff --git a/Src/IACore/inc/IACore/DataOps.hpp b/Src/IACore/inc/IACore/DataOps.hpp
new file mode 100644
index 0000000..b0d5050
--- /dev/null
+++ b/Src/IACore/inc/IACore/DataOps.hpp
@@ -0,0 +1,32 @@
+// IACore-OSS; The Core Library for All IA Open Source Projects
+// Copyright (C) 2025 IAS (ias@iasoft.dev)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#pragma once
+
+#include
+
+namespace IACore
+{
+ class DataOps
+ {
+ public:
+ STATIC Expected, String> ZlibInflate(IN Span data);
+ STATIC Expected, String> ZlibDeflate(IN Span data);
+
+ STATIC Expected, String> ZstdInflate(IN Span data);
+ STATIC Expected, String> ZstdDeflate(IN Span data);
+ };
+}
\ No newline at end of file
diff --git a/Src/IACore/inc/IACore/HttpClient.hpp b/Src/IACore/inc/IACore/HttpClient.hpp
new file mode 100644
index 0000000..0cd85ee
--- /dev/null
+++ b/Src/IACore/inc/IACore/HttpClient.hpp
@@ -0,0 +1,195 @@
+// IACore-OSS; The Core Library for All IA Open Source Projects
+// Copyright (C) 2025 IAS (ias@iasoft.dev)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#pragma once
+
+#include
+
+namespace IACore
+{
+ class HttpClient
+ {
+ public:
+ enum class EHeaderType
+ {
+ ACCEPT,
+ ACCEPT_CHARSET,
+ ACCEPT_ENCODING,
+ ACCEPT_LANGUAGE,
+ AUTHORIZATION,
+ CACHE_CONTROL,
+ CONNECTION,
+ CONTENT_LENGTH,
+ CONTENT_TYPE,
+ COOKIE,
+ DATE,
+ EXPECT,
+ HOST,
+ IF_MATCH,
+ IF_MODIFIED_SINCE,
+ IF_NONE_MATCH,
+ ORIGIN,
+ PRAGMA,
+ PROXY_AUTHORIZATION,
+ RANGE,
+ REFERER,
+ TE,
+ UPGRADE,
+ USER_AGENT,
+ VIA,
+ WARNING
+ };
+
+ enum class EResponseCode : INT32
+ {
+ // 1xx Informational
+ CONTINUE = 100,
+ SWITCHING_PROTOCOLS = 101,
+ PROCESSING = 102,
+ EARLY_HINTS = 103,
+
+ // 2xx Success
+ OK = 200,
+ CREATED = 201,
+ ACCEPTED = 202,
+ NON_AUTHORITATIVE_INFORMATION = 203,
+ NO_CONTENT = 204,
+ RESET_CONTENT = 205,
+ PARTIAL_CONTENT = 206,
+ MULTI_STATUS = 207,
+ ALREADY_REPORTED = 208,
+ IM_USED = 226,
+
+ // 3xx Redirection
+ MULTIPLE_CHOICES = 300,
+ MOVED_PERMANENTLY = 301,
+ FOUND = 302,
+ SEE_OTHER = 303,
+ NOT_MODIFIED = 304,
+ USE_PROXY = 305,
+ TEMPORARY_REDIRECT = 307,
+ PERMANENT_REDIRECT = 308,
+
+ // 4xx Client Error
+ BAD_REQUEST = 400,
+ UNAUTHORIZED = 401,
+ PAYMENT_REQUIRED = 402,
+ FORBIDDEN = 403,
+ NOT_FOUND = 404,
+ METHOD_NOT_ALLOWED = 405,
+ NOT_ACCEPTABLE = 406,
+ PROXY_AUTHENTICATION_REQUIRED = 407,
+ REQUEST_TIMEOUT = 408,
+ CONFLICT = 409,
+ GONE = 410,
+ LENGTH_REQUIRED = 411,
+ PRECONDITION_FAILED = 412,
+ PAYLOAD_TOO_LARGE = 413,
+ URI_TOO_LONG = 414,
+ UNSUPPORTED_MEDIA_TYPE = 415,
+ RANGE_NOT_SATISFIABLE = 416,
+ EXPECTATION_FAILED = 417,
+ IM_A_TEAPOT = 418,
+ MISDIRECTED_REQUEST = 421,
+ UNPROCESSABLE_ENTITY = 422,
+ LOCKED = 423,
+ FAILED_DEPENDENCY = 424,
+ TOO_EARLY = 425,
+ UPGRADE_REQUIRED = 426,
+ PRECONDITION_REQUIRED = 428,
+ TOO_MANY_REQUESTS = 429,
+ REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
+ UNAVAILABLE_FOR_LEGAL_REASONS = 451,
+
+ // 5xx Server Error
+ INTERNAL_SERVER_ERROR = 500,
+ NOT_IMPLEMENTED = 501,
+ BAD_GATEWAY = 502,
+ SERVICE_UNAVAILABLE = 503,
+ GATEWAY_TIMEOUT = 504,
+ HTTP_VERSION_NOT_SUPPORTED = 505,
+ VARIANT_ALSO_NEGOTIATES = 506,
+ INSUFFICIENT_STORAGE = 507,
+ LOOP_DETECTED = 508,
+ NOT_EXTENDED = 510,
+ NETWORK_AUTHENTICATION_REQUIRED = 511
+ };
+
+ using Header = KeyValuePair;
+
+ public:
+ HttpClient(IN CONST String &host);
+ ~HttpClient();
+
+ public:
+ // Automatically adds the following headers, if not present:
+ // 1) EHeaderType::CONTENT_TYPE = defaultContentType
+ Expected RawGet(IN CONST String &path, IN Span headers,
+ IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
+ // Automatically adds the following headers, if not present:
+ // 1) EHeaderType::CONTENT_TYPE = defaultContentType
+ Expected RawPost(IN CONST String &path, IN Span headers, IN CONST String &body,
+ IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
+
+ template
+ Expected<_response_type, String> JsonGet(IN CONST String &path, IN Span headers);
+
+ template
+ Expected<_response_type, String> JsonPost(IN CONST String &path, IN Span headers,
+ IN CONST _payload_type &body);
+
+ public:
+ STATIC String UrlEncode(IN CONST String &value);
+ STATIC String UrlDecode(IN CONST String &value);
+
+ STATIC String HeaderTypeToString(IN EHeaderType type);
+ STATIC Header CreateHeader(IN EHeaderType key, IN CONST String &value);
+
+ public:
+ EResponseCode LastResponseCode()
+ {
+ return m_lastResponseCode;
+ }
+
+ private:
+ PVOID m_client{};
+ EResponseCode m_lastResponseCode;
+ };
+
+ template
+ Expected<_response_type, String> HttpClient::JsonGet(IN CONST String &path, IN Span headers)
+ {
+ const auto rawResponse = RawGet(path, headers, "application/json");
+ if (!rawResponse)
+ return MakeUnexpected(rawResponse.error());
+ if (LastResponseCode() != EResponseCode::OK)
+ return MakeUnexpected(std::format("Server responded with code {}", (INT32) LastResponseCode()));
+ return JSON::ParseToStruct<_response_type>(*rawResponse);
+ }
+
+ template
+ Expected<_response_type, String> HttpClient::JsonPost(IN CONST String &path, IN Span headers,
+ IN CONST _payload_type &body)
+ {
+ const auto encodedBody = IA_TRY(JSON::EncodeStruct(body));
+ const auto rawResponse = RawPost(path, headers, encodedBody, "application/json");
+ if (!rawResponse)
+ return MakeUnexpected(rawResponse.error());
+ if (LastResponseCode() != EResponseCode::OK)
+ return MakeUnexpected(std::format("Server responded with code {}", (INT32) LastResponseCode()));
+ return JSON::ParseToStruct<_response_type>(*rawResponse);
+ }
+} // namespace IACore
\ No newline at end of file
diff --git a/Src/IACore/inc/IACore/JSON.hpp b/Src/IACore/inc/IACore/JSON.hpp
new file mode 100644
index 0000000..e6b779b
--- /dev/null
+++ b/Src/IACore/inc/IACore/JSON.hpp
@@ -0,0 +1,58 @@
+// IACore-OSS; The Core Library for All IA Open Source Projects
+// Copyright (C) 2025 IAS (ias@iasoft.dev)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#pragma once
+
+#include
+
+#include
+#include
+#include
+
+namespace IACore
+{
+ class JSON
+ {
+ private:
+ STATIC CONSTEXPR AUTO GLAZE_JSON_OPTS = glz::opts{.error_on_unknown_keys = false};
+
+ public:
+ STATIC Expected Parse(IN CONST String &json);
+ STATIC Expected ParseReadOnly(IN CONST String &json);
+ template STATIC Expected<_object_type, String> ParseToStruct(IN CONST String &json);
+
+ STATIC String Encode(IN nlohmann::json data);
+ template STATIC Expected EncodeStruct(IN CONST _object_type &data);
+ };
+
+ template Expected<_object_type, String> JSON::ParseToStruct(IN CONST String &json)
+ {
+ _object_type result{};
+ const auto parseError = glz::read(result, json);
+ if (parseError)
+ return MakeUnexpected(std::format("JSON Error: {}", glz::format_error(parseError, json)));
+ return result;
+ }
+
+ template Expected JSON::EncodeStruct(IN CONST _object_type &data)
+ {
+ String result;
+ const auto encodeError = glz::write_json(data, result);
+ if (encodeError)
+ return MakeUnexpected(std::format("JSON Error: {}", glz::format_error(encodeError)));
+ return result;
+ }
+} // namespace IACore
\ No newline at end of file
diff --git a/Src/IACore/inc/IACore/PCH.hpp b/Src/IACore/inc/IACore/PCH.hpp
index 5e4ec4b..c3bc514 100644
--- a/Src/IACore/inc/IACore/PCH.hpp
+++ b/Src/IACore/inc/IACore/PCH.hpp
@@ -33,6 +33,7 @@
# include
# include
# include
+# include
# include
# include
# include
@@ -540,6 +541,7 @@ template using UniquePtr = std::unique_ptr<_value_type>;
template using Deque = std::deque<_value_type>;
template using Pair = std::pair<_type_a, _type_b>;
template using Tuple = std::tuple;
+template using KeyValuePair = std::pair<_key_type, _value_type>;
template
using Expected = tl::expected<_expected_type, _unexpected_type>;
diff --git a/Vendor/CMakeLists.txt b/Vendor/CMakeLists.txt
deleted file mode 100644
index 52d1203..0000000
--- a/Vendor/CMakeLists.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-
-set(EXPECTED_BUILD_TESTS OFF)
-add_subdirectory(expected/)
-
-set(MI_BUILD_TESTS OFF)
-set(MI_BUILD_STATIC ON)
-set(MI_BUILD_SHARED ON)
-add_subdirectory(mimalloc/)
-
-add_subdirectory(unordered_dense/)
diff --git a/Vendor/expected b/Vendor/expected
deleted file mode 160000
index 1770e35..0000000
--- a/Vendor/expected
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 1770e3559f2f6ea4a5fb4f577ad22aeb30fbd8e4
diff --git a/Vendor/mimalloc b/Vendor/mimalloc
deleted file mode 160000
index 09a2709..0000000
--- a/Vendor/mimalloc
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 09a27098aa6e9286518bd9c74e6ffa7199c3f04e
diff --git a/Vendor/unordered_dense b/Vendor/unordered_dense
deleted file mode 160000
index 3234af2..0000000
--- a/Vendor/unordered_dense
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 3234af2c03549bc85656bfd3a86993bf1cd8aef1