Skip to content

Commit a0c98a9

Browse files
committed
Merge branch '1902-DAPS-core-update-test-api-schema-client' into 1830-1902-merge
2 parents b768cbc + 5292b7a commit a0c98a9

14 files changed

Lines changed: 1226 additions & 96 deletions

core/database/foxx/api/schema_router.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ function parseSchemaId(schId) {
5858
}
5959

6060
if (colonCount === 0) {
61-
return { id: schId, ver: 0 };
61+
return { id: schId, ver: null };
6262
}
6363

6464
const idx = schId.indexOf(":");

core/server/ISchemaStorage.hpp

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,23 @@ class ISchemaStorage {
5555
/**
5656
* @brief Store schema content on create.
5757
*
58-
* @param a_id Schema ID (matches Arango document ID).
59-
* @param a_content The raw schema definition text.
60-
* @param a_desc Human-readable description (storage may use this).
61-
* @param log_context Logging/correlation context.
58+
* @param a_id Schema ID (matches Arango document ID).
59+
* @param a_content The raw schema definition text.
60+
* @param a_desc Human-readable description (storage may use this).
61+
* @param a_schema_format Serialization format ("json", "yaml", "xml").
62+
* @param a_engine Validation engine / schema type ("JSONSchema",
63+
* "LinkML", etc.).
64+
* @param a_version Semantic version string (empty if not versioned).
65+
* @param log_context Logging/correlation context.
6266
* @return The value to write into Arango's `def` field.
6367
* @throws TraceException on failure.
6468
*/
6569
virtual std::string storeContent(const std::string &a_id,
6670
const std::string &a_content,
6771
const std::string &a_desc,
72+
const std::string &a_schema_format,
73+
const std::string &a_engine,
74+
const std::string &a_version,
6875
LogContext log_context) = 0;
6976

7077
/**
@@ -83,16 +90,22 @@ class ISchemaStorage {
8390
/**
8491
* @brief Update schema content.
8592
*
86-
* @param a_id Schema ID.
87-
* @param a_content New schema definition text.
88-
* @param a_desc Updated description (nullopt if unchanged).
89-
* @param log_context Logging/correlation context.
93+
* @param a_id Schema ID.
94+
* @param a_content New schema definition text.
95+
* @param a_desc Updated description (nullopt if unchanged).
96+
* @param a_schema_format Updated format (nullopt if unchanged).
97+
* @param a_engine Updated engine (nullopt if unchanged).
98+
* @param a_version Updated version (nullopt if unchanged).
99+
* @param log_context Logging/correlation context.
90100
* @return The value to write into Arango's `def` field.
91101
* @throws TraceException on failure.
92102
*/
93103
virtual std::string updateContent(const std::string &a_id,
94104
const std::string &a_content,
95105
const std::optional<std::string> &a_desc,
106+
const std::optional<std::string> &a_schema_format,
107+
const std::optional<std::string> &a_engine,
108+
const std::optional<std::string> &a_version,
96109
LogContext log_context) = 0;
97110

98111
/**

core/server/SchemaAPIClient.cpp

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ SchemaAPIClient::SchemaAPIClient(const SchemaAPIConfig &a_config)
4141
curl_easy_setopt(m_curl, CURLOPT_CAINFO, m_config.ca_cert_path.c_str());
4242

4343
// ── Timeouts ──────────────────────────────────────────────────────────
44-
curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, m_config.connect_timeout_sec);
44+
curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT,
45+
m_config.connect_timeout_sec);
4546
curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, m_config.request_timeout_sec);
4647
}
4748

@@ -50,6 +51,15 @@ SchemaAPIClient::~SchemaAPIClient() {
5051
curl_easy_cleanup(m_curl);
5152
}
5253

54+
// ── Custom Headers ──────────────────────────────────────────────────────────
55+
56+
void SchemaAPIClient::setCustomHeaders(
57+
const std::map<std::string, std::string> &a_headers) {
58+
m_custom_headers = a_headers;
59+
}
60+
61+
void SchemaAPIClient::clearCustomHeaders() { m_custom_headers.clear(); }
62+
5363
// ── Low-level CURL ──────────────────────────────────────────────────────────
5464

5565
nlohmann::json SchemaAPIClient::curlPerform(const std::string &a_method,
@@ -74,13 +84,20 @@ nlohmann::json SchemaAPIClient::curlPerform(const std::string &a_method,
7484
headers = curl_slist_append(headers, "Accept: application/json");
7585

7686
if (m_config.hasAuth()) {
77-
std::string auth = "Authorization: Bearer " + m_config.bearer_token;
78-
headers = curl_slist_append(headers, auth.c_str());
87+
std::string auth = "Authorization: Bearer " + m_config.bearer_token;
88+
headers = curl_slist_append(headers, auth.c_str());
7989
}
8090

8191
std::string corr_header = "x-correlation-id: " + log_context.correlation_id;
8292
headers = curl_slist_append(headers, corr_header.c_str());
8393

94+
// Append any custom headers (used by integration tests for Prism's
95+
// Prefer header, etc.)
96+
for (const auto &[name, value] : m_custom_headers) {
97+
std::string h = name + ": " + value;
98+
headers = curl_slist_append(headers, h.c_str());
99+
}
100+
84101
curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, headers);
85102

86103
if (a_body && !a_body->empty()) {
@@ -125,8 +142,8 @@ nlohmann::json SchemaAPIClient::curlPerform(const std::string &a_method,
125142
nlohmann::json SchemaAPIClient::httpGet(const std::string &a_path,
126143
LogContext log_context) {
127144
long code = 0;
128-
auto result =
129-
curlPerform("GET", m_config.base_url + a_path, nullptr, code, log_context);
145+
auto result = curlPerform("GET", m_config.base_url + a_path, nullptr, code,
146+
log_context);
130147
if (code == 404)
131148
EXCEPT_PARAM(BAD_REQUEST, "SchemaAPI: not found: " << a_path);
132149
if (code < 200 || code >= 300)
@@ -139,17 +156,17 @@ nlohmann::json SchemaAPIClient::httpPost(const std::string &a_path,
139156
long &a_http_code,
140157
LogContext log_context) {
141158
std::string body_str = a_body.dump();
142-
return curlPerform("POST", m_config.base_url + a_path, &body_str, a_http_code,
143-
log_context);
159+
return curlPerform("POST", m_config.base_url + a_path, &body_str,
160+
a_http_code, log_context);
144161
}
145162

146163
nlohmann::json SchemaAPIClient::httpPut(const std::string &a_path,
147164
const nlohmann::json &a_body,
148165
LogContext log_context) {
149166
long code = 0;
150167
std::string body_str = a_body.dump();
151-
auto result =
152-
curlPerform("PUT", m_config.base_url + a_path, &body_str, code, log_context);
168+
auto result = curlPerform("PUT", m_config.base_url + a_path, &body_str, code,
169+
log_context);
153170
if (code < 200 || code >= 300)
154171
EXCEPT_PARAM(SERVICE_ERROR, "SchemaAPI PUT failed, HTTP " << code);
155172
return result;
@@ -160,8 +177,8 @@ nlohmann::json SchemaAPIClient::httpPatch(const std::string &a_path,
160177
LogContext log_context) {
161178
long code = 0;
162179
std::string body_str = a_body.dump();
163-
auto result =
164-
curlPerform("PATCH", m_config.base_url + a_path, &body_str, code, log_context);
180+
auto result = curlPerform("PATCH", m_config.base_url + a_path, &body_str,
181+
code, log_context);
165182
if (code == 404)
166183
EXCEPT_PARAM(BAD_REQUEST, "SchemaAPI: not found for PATCH");
167184
if (code < 200 || code >= 300)
@@ -172,7 +189,8 @@ nlohmann::json SchemaAPIClient::httpPatch(const std::string &a_path,
172189
void SchemaAPIClient::httpDelete(const std::string &a_path,
173190
LogContext log_context) {
174191
long code = 0;
175-
curlPerform("DELETE", m_config.base_url + a_path, nullptr, code, log_context);
192+
curlPerform("DELETE", m_config.base_url + a_path, nullptr, code,
193+
log_context);
176194
if (code != 204 && code != 404)
177195
EXCEPT_PARAM(SERVICE_ERROR, "SchemaAPI DELETE failed, HTTP " << code);
178196
}
@@ -182,31 +200,48 @@ void SchemaAPIClient::httpDelete(const std::string &a_path,
182200
void SchemaAPIClient::putSchema(const std::string &a_id,
183201
const std::string &a_name,
184202
const std::string &a_description,
203+
const std::string &a_schema_format,
204+
const std::string &a_engine,
185205
const std::string &a_content,
206+
const std::string &a_version,
186207
LogContext log_context) {
187208
nlohmann::json body;
188-
body["id"] = a_id;
209+
// Required fields per SchemaReplace
189210
body["name"] = a_name;
211+
body["schema_format"] = a_schema_format;
212+
body["engine"] = a_engine;
190213
body["content"] = a_content;
214+
215+
// Optional fields — only include when non-empty
191216
if (!a_description.empty())
192217
body["description"] = a_description;
218+
if (!a_version.empty())
219+
body["version"] = a_version;
193220

194221
httpPut("/schemas/" + a_id, body, log_context);
195222
}
196223

197-
void SchemaAPIClient::patchSchema(const std::string &a_id,
198-
const std::optional<std::string> &a_name,
199-
const std::optional<std::string> &a_description,
200-
const std::optional<std::string> &a_content,
201-
LogContext log_context) {
202-
nlohmann::json body;
203-
body["id"] = a_id;
224+
void SchemaAPIClient::patchSchema(
225+
const std::string &a_id, const std::optional<std::string> &a_name,
226+
const std::optional<std::string> &a_description,
227+
const std::optional<std::string> &a_schema_format,
228+
const std::optional<std::string> &a_engine,
229+
const std::optional<std::string> &a_content,
230+
const std::optional<std::string> &a_version, LogContext log_context) {
231+
nlohmann::json body = nlohmann::json::object();
232+
204233
if (a_name)
205234
body["name"] = *a_name;
206235
if (a_description)
207236
body["description"] = *a_description;
237+
if (a_schema_format)
238+
body["schema_format"] = *a_schema_format;
239+
if (a_engine)
240+
body["engine"] = *a_engine;
208241
if (a_content)
209242
body["content"] = *a_content;
243+
if (a_version)
244+
body["version"] = *a_version;
210245

211246
httpPatch("/schemas/" + a_id, body, log_context);
212247
}
@@ -254,7 +289,6 @@ bool SchemaAPIClient::validateMetadata(const std::string &a_schema_id,
254289
std::string &a_warnings,
255290
LogContext log_context) {
256291
nlohmann::json body;
257-
body["id"] = a_schema_id;
258292
body["metadata_format"] = a_metadata_format;
259293
body["engine"] = a_engine;
260294
body["content"] = a_metadata_content;

core/server/SchemaAPIClient.hpp

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <curl/curl.h>
99
#include <nlohmann/json.hpp>
1010

11+
#include <map>
1112
#include <optional>
1213
#include <string>
1314

@@ -35,38 +36,87 @@ class SchemaAPIClient {
3536

3637
bool isConfigured() const { return !m_config.base_url.empty(); }
3738

39+
// ── Custom Headers ────────────────────────────────────────────────────
40+
41+
/**
42+
* @brief Set additional HTTP headers appended to every request.
43+
*
44+
* Replaces any previously set custom headers. Headers persist across
45+
* requests until replaced or cleared.
46+
*
47+
* Intended for integration testing (e.g. Prism's Prefer header to select
48+
* response examples/status codes). Production code should not need this.
49+
*
50+
* @param a_headers Map of header name → value.
51+
*/
52+
void setCustomHeaders(const std::map<std::string, std::string> &a_headers);
53+
54+
/**
55+
* @brief Remove all custom headers.
56+
*/
57+
void clearCustomHeaders();
58+
3859
// ── Storage Operations ────────────────────────────────────────────────
3960

4061
/**
41-
* @brief Create or replace a schema at the given ID.
62+
* @brief Create or replace a schema at the given ID (PUT /schemas/{id}).
63+
*
64+
* Per OpenAPI spec (SchemaReplace), required body fields:
65+
* name, schema_format, engine, content
66+
* Optional body fields: description, version, revise
67+
*
68+
* @param a_id Schema identifier (path parameter only).
69+
* @param a_name Schema name (required).
70+
* @param a_description Schema description (omitted from body if empty).
71+
* @param a_schema_format Serialization format: "json", "yaml", "xml".
72+
* @param a_engine Validation engine: "JSONSchema", "LinkML", etc.
73+
* @param a_content Raw schema content as text.
74+
* @param a_version Semantic version string (omitted from body if empty).
75+
* @param log_context Logging context.
4276
*/
4377
void putSchema(const std::string &a_id, const std::string &a_name,
4478
const std::string &a_description,
45-
const std::string &a_content, LogContext log_context);
79+
const std::string &a_schema_format,
80+
const std::string &a_engine, const std::string &a_content,
81+
const std::string &a_version, LogContext log_context);
4682

4783
/**
48-
* @brief Partially update a schema.
84+
* @brief Partially update a schema (PATCH /schemas/{id}).
85+
*
86+
* Per OpenAPI spec (SchemaPatch), all body fields are optional.
87+
*
88+
* @param a_id Schema identifier (path parameter only).
89+
* @param a_name Updated name (nullopt = unchanged).
90+
* @param a_description Updated description (nullopt = unchanged).
91+
* @param a_schema_format Updated format (nullopt = unchanged).
92+
* @param a_engine Updated engine (nullopt = unchanged).
93+
* @param a_content Updated content (nullopt = unchanged).
94+
* @param a_version Updated version (nullopt = unchanged).
95+
* @param log_context Logging context.
4996
*/
5097
void patchSchema(const std::string &a_id,
5198
const std::optional<std::string> &a_name,
5299
const std::optional<std::string> &a_description,
100+
const std::optional<std::string> &a_schema_format,
101+
const std::optional<std::string> &a_engine,
53102
const std::optional<std::string> &a_content,
103+
const std::optional<std::string> &a_version,
54104
LogContext log_context);
55105

56106
/**
57-
* @brief Retrieve a schema by ID.
107+
* @brief Retrieve a schema by ID (GET /schemas/{id}).
58108
*/
59109
nlohmann::json getSchema(const std::string &a_id, LogContext log_context);
60110

61111
/**
62-
* @brief Delete a schema by ID.
112+
* @brief Delete a schema by ID (DELETE /schemas/{id}).
63113
*/
64114
void deleteSchema(const std::string &a_id, LogContext log_context);
65115

66116
// ── Validation Operations ─────────────────────────────────────────────
67117

68118
/**
69-
* @brief Validate a schema definition.
119+
* @brief Validate a schema definition (POST /schemas/validate).
70120
*
71121
* @param a_schema_format Serialization format ("json", "yaml", "xml").
72122
* @param a_engine Validation engine ("JSONSchema", "LinkML", etc.).
@@ -81,9 +131,10 @@ class SchemaAPIClient {
81131
LogContext log_context);
82132

83133
/**
84-
* @brief Validate metadata against a stored schema.
134+
* @brief Validate metadata against a stored schema
135+
* (POST /schemas/{id}/validate).
85136
*
86-
* @param a_schema_id Schema ID to validate against.
137+
* @param a_schema_id Schema ID (path parameter only).
87138
* @param a_metadata_format Format of the metadata ("json", "yaml").
88139
* @param a_engine Validation engine to use.
89140
* @param a_metadata_content Metadata to validate.
@@ -119,6 +170,7 @@ class SchemaAPIClient {
119170

120171
SchemaAPIConfig m_config;
121172
CURL *m_curl;
173+
std::map<std::string, std::string> m_custom_headers;
122174
};
123175

124176
} // namespace Core

0 commit comments

Comments
 (0)