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+ }
0 commit comments