Skip to content

Commit 9552645

Browse files
Add System, Properties, TimeExtent classes, APIRequest and APIResponse
1 parent edaeb1f commit 9552645

20 files changed

Lines changed: 897 additions & 21 deletions

CSAPI-lib/APIRequest.h

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <iostream>
5+
#include <stdexcept>
6+
#include <curl/curl.h>
7+
#include <map>
8+
#include "APIResponse.h"
9+
10+
namespace ConnectedSystemsAPI {
11+
class APIRequest {
12+
private:
13+
std::string apiRoot;
14+
std::string endpoint;
15+
std::string requestMethod;
16+
std::map<std::string, std::string> headers;
17+
std::string body;
18+
19+
APIRequest(const std::string& apiRoot, const std::string& endpoint, const std::string& requestMethod, const std::map<std::string, std::string>& headers, const std::string& body)
20+
: apiRoot(apiRoot), endpoint(endpoint), requestMethod(requestMethod), headers(headers), body(body) {
21+
}
22+
23+
public:
24+
template<typename T>
25+
APIResponse<T> execute() {
26+
std::string rawResponse = execute();
27+
return APIResponse<T>(0, "", rawResponse, {});
28+
}
29+
30+
std::string execute() const {
31+
CURL* curl = curl_easy_init();
32+
std::string response;
33+
if (curl) {
34+
std::string url = apiRoot + endpoint;
35+
struct curl_slist* header_list = nullptr;
36+
for (const auto& h : headers) {
37+
header_list = curl_slist_append(header_list, (h.first + ": " + h.second).c_str());
38+
}
39+
40+
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
41+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
42+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
43+
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
44+
45+
if (requestMethod == "POST") {
46+
curl_easy_setopt(curl, CURLOPT_POST, 1L);
47+
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
48+
}
49+
else if (requestMethod == "PUT") {
50+
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
51+
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
52+
}
53+
else if (requestMethod == "DELETE") {
54+
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
55+
}
56+
57+
CURLcode res = curl_easy_perform(curl);
58+
if (res != CURLE_OK)
59+
std::cerr << "cURL error: " << curl_easy_strerror(res) << std::endl;
60+
61+
curl_slist_free_all(header_list);
62+
curl_easy_cleanup(curl);
63+
}
64+
return response;
65+
}
66+
67+
private:
68+
static size_t WriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata) {
69+
auto* response = static_cast<std::string*>(userdata);
70+
response->append(ptr, size * nmemb);
71+
return size * nmemb;
72+
}
73+
74+
public:
75+
// Builder inner class
76+
class Builder {
77+
private:
78+
std::string apiRoot;
79+
std::string requestMethod = "GET";
80+
std::map<std::string, std::string> headers;
81+
std::string body;
82+
std::string resourcePath;
83+
std::string resourceId;
84+
std::string subResourcePath;
85+
std::string subResourceId;
86+
87+
public:
88+
Builder& setApiRoot(const std::string& root) {
89+
this->apiRoot = root;
90+
return *this;
91+
}
92+
93+
Builder& setMethod(const std::string& requestMethod) {
94+
this->requestMethod = requestMethod;
95+
return *this;
96+
}
97+
98+
Builder& setAuthHeader(const std::string& authHeader) {
99+
this->headers["Authorization"] = authHeader;
100+
return *this;
101+
}
102+
103+
Builder& addHeader(const std::string& key, const std::string& value) {
104+
this->headers[key] = value;
105+
return *this;
106+
}
107+
108+
Builder& setBody(const std::string& body) {
109+
this->body = body;
110+
return *this;
111+
}
112+
113+
Builder& setResourcePath(const std::string& resourcePath) {
114+
this->resourcePath = resourcePath;
115+
return *this;
116+
}
117+
118+
Builder& setResourceId(const std::string& resourceId) {
119+
this->resourceId = resourceId;
120+
return *this;
121+
}
122+
123+
Builder& setSubResourcePath(const std::string& subResourcePath) {
124+
this->subResourcePath = subResourcePath;
125+
return *this;
126+
}
127+
128+
Builder& setSubResourceId(const std::string& subResourceId) {
129+
this->subResourceId = subResourceId;
130+
return *this;
131+
}
132+
133+
APIRequest build() {
134+
if (apiRoot.empty()) {
135+
throw std::invalid_argument("API root must be set");
136+
}
137+
if (requestMethod.empty()) {
138+
throw std::invalid_argument("Request method must be set.");
139+
}
140+
141+
std::string endpoint;
142+
endpoint = appendPath(endpoint, resourcePath);
143+
endpoint = appendPath(endpoint, resourceId);
144+
endpoint = appendPath(endpoint, subResourcePath);
145+
endpoint = appendPath(endpoint, subResourceId);
146+
147+
return APIRequest(apiRoot, endpoint, requestMethod, headers, body);
148+
}
149+
150+
private:
151+
static std::string appendPath(const std::string& base, const std::string& path) {
152+
size_t start = 0;
153+
size_t end = path.size();
154+
155+
// Trim leading slashes
156+
while (start < end && path[start] == '/')
157+
++start;
158+
// Trim trailing slashes
159+
while (end > start && path[end - 1] == '/')
160+
--end;
161+
162+
std::string trimmed = path.substr(start, end - start);
163+
if (trimmed.empty())
164+
return base;
165+
166+
if (base.empty())
167+
return "/" + trimmed;
168+
169+
// Ensure single slash between base and trimmed path
170+
if (base.back() == '/')
171+
return base + trimmed;
172+
else
173+
return base + "/" + trimmed;
174+
}
175+
};
176+
};
177+
}

CSAPI-lib/APIResponse.h

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <vector>
5+
#include <map>
6+
#include <nlohmann/json.hpp>
7+
8+
namespace ConnectedSystemsAPI {
9+
template <typename T>
10+
class APIResponse {
11+
private:
12+
static constexpr const char* JSON_ARRAY_ITEMS = "items";
13+
14+
int responseCode;
15+
std::string responseMessage;
16+
std::string responseBody;
17+
std::vector<T> items;
18+
bool isSingleItem;
19+
std::map<std::string, std::vector<std::string>> headers;
20+
21+
T deserializeItem(const nlohmann::json& jsonElement) {
22+
return jsonElement.get<T>();
23+
}
24+
25+
public:
26+
APIResponse(int responseCode, const std::string& responseMessage, const std::string& responseBody, const std::map<std::string, std::vector<std::string>>& headers)
27+
: responseCode(responseCode), responseMessage(responseMessage), responseBody(responseBody), headers(headers), isSingleItem(false) {
28+
if (responseBody.empty() || responseBody[0] != '{') {
29+
isSingleItem = false;
30+
return;
31+
}
32+
33+
auto jsonObj = nlohmann::json::parse(responseBody);
34+
if (jsonObj.contains(JSON_ARRAY_ITEMS)) {
35+
isSingleItem = false;
36+
for (const auto& itemElement : jsonObj[JSON_ARRAY_ITEMS]) {
37+
items.push_back(deserializeItem(itemElement));
38+
}
39+
}
40+
else {
41+
isSingleItem = true;
42+
items.push_back(deserializeItem(jsonObj));
43+
}
44+
}
45+
46+
bool isSuccessful() const {
47+
return responseCode >= 200 && responseCode < 400;
48+
}
49+
50+
int getResponseCode() const { return responseCode; }
51+
const std::string& getResponseMessage() const { return responseMessage; }
52+
const std::string& getResponseBody() const { return responseBody; }
53+
bool getIsSingleItem() const { return isSingleItem; }
54+
const std::map<std::string, std::vector<std::string>>& getHeaders() const { return headers; }
55+
56+
T getItem() const {
57+
return (isSingleItem && !items.empty()) ? items[0] : T{};
58+
}
59+
60+
const std::vector<T>& getItems() const {
61+
return items;
62+
}
63+
};
64+
}

ConnectedSystemsAPI-CPP/ConnectedSystemsAPI-CPP.vcxproj renamed to CSAPI-lib/CSAPI-lib.vcxproj

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
<PropertyGroup Label="Globals">
2222
<VCProjectVersion>17.0</VCProjectVersion>
2323
<Keyword>Win32Proj</Keyword>
24-
<ProjectGuid>{79a4b6b0-9ca1-42aa-9b79-3b5cb91252f3}</ProjectGuid>
25-
<RootNamespace>ConnectedSystemsAPICPP</RootNamespace>
24+
<ProjectGuid>{7aedcc9e-20ec-40c7-a6e7-dbdb997ff9c8}</ProjectGuid>
25+
<RootNamespace>CSAPIlib</RootNamespace>
2626
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
2727
</PropertyGroup>
2828
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
@@ -104,6 +104,7 @@
104104
<SDLCheck>true</SDLCheck>
105105
<PreprocessorDefinitions>_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
106106
<ConformanceMode>true</ConformanceMode>
107+
<LanguageStandard>stdcpp17</LanguageStandard>
107108
</ClCompile>
108109
<Link>
109110
<SubSystem>
@@ -119,6 +120,7 @@
119120
<SDLCheck>true</SDLCheck>
120121
<PreprocessorDefinitions>NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
121122
<ConformanceMode>true</ConformanceMode>
123+
<LanguageStandard>stdcpp17</LanguageStandard>
122124
</ClCompile>
123125
<Link>
124126
<SubSystem>
@@ -127,10 +129,16 @@
127129
</Link>
128130
</ItemDefinitionGroup>
129131
<ItemGroup>
132+
<ClInclude Include="APIRequest.h" />
133+
<ClInclude Include="APIResponse.h" />
134+
<ClInclude Include="ConnectedSystemsAPI.h" />
130135
<ClInclude Include="framework.h" />
131-
</ItemGroup>
132-
<ItemGroup>
133-
<ClCompile Include="ConnectedSystemsAPI.cpp" />
136+
<ClInclude Include="OptionalTimePoint.h" />
137+
<ClInclude Include="Properties.h" />
138+
<ClInclude Include="System.h" />
139+
<ClInclude Include="SystemsAPI.h" />
140+
<ClInclude Include="TimeExtent.h" />
141+
<ClInclude Include="TimeUtils.h" />
134142
</ItemGroup>
135143
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
136144
<ImportGroup Label="ExtensionTargets">
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<ItemGroup>
4+
<Filter Include="Source Files">
5+
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
6+
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
7+
</Filter>
8+
<Filter Include="Header Files">
9+
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
10+
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
11+
</Filter>
12+
<Filter Include="Resource Files">
13+
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
14+
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
15+
</Filter>
16+
<Filter Include="Header Files\DataModels">
17+
<UniqueIdentifier>{bb0abcb8-4aa1-4f6b-8adf-dd2d64f1188e}</UniqueIdentifier>
18+
</Filter>
19+
<Filter Include="Header Files\Util">
20+
<UniqueIdentifier>{5f2184e7-00be-4a1b-b6ca-531629b94bb6}</UniqueIdentifier>
21+
</Filter>
22+
</ItemGroup>
23+
<ItemGroup>
24+
<ClInclude Include="framework.h">
25+
<Filter>Header Files</Filter>
26+
</ClInclude>
27+
<ClInclude Include="APIRequest.h">
28+
<Filter>Header Files</Filter>
29+
</ClInclude>
30+
<ClInclude Include="APIResponse.h">
31+
<Filter>Header Files</Filter>
32+
</ClInclude>
33+
<ClInclude Include="ConnectedSystemsAPI.h">
34+
<Filter>Header Files</Filter>
35+
</ClInclude>
36+
<ClInclude Include="OptionalTimePoint.h">
37+
<Filter>Header Files</Filter>
38+
</ClInclude>
39+
<ClInclude Include="SystemsAPI.h">
40+
<Filter>Header Files</Filter>
41+
</ClInclude>
42+
<ClInclude Include="Properties.h">
43+
<Filter>Header Files\DataModels</Filter>
44+
</ClInclude>
45+
<ClInclude Include="System.h">
46+
<Filter>Header Files\DataModels</Filter>
47+
</ClInclude>
48+
<ClInclude Include="TimeExtent.h">
49+
<Filter>Header Files\DataModels</Filter>
50+
</ClInclude>
51+
<ClInclude Include="TimeUtils.h">
52+
<Filter>Header Files\Util</Filter>
53+
</ClInclude>
54+
</ItemGroup>
55+
</Project>

ConnectedSystemsAPI-CPP/ConnectedSystemsAPI-CPP.vcxproj.user renamed to CSAPI-lib/CSAPI-lib.vcxproj.user

File renamed without changes.

0 commit comments

Comments
 (0)