Skip to content
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
eb403ff
refactor: core server files refactored to support re-use in the mock …
JoshuaSBrown Feb 19, 2026
0eb3bf1
refactor: changed mock core server to work with proto3 and reusue abs…
JoshuaSBrown Feb 19, 2026
354bd19
fix: prevent defaults being set to undefined, and interpret numbers a…
JoshuaSBrown Feb 23, 2026
eba87f5
fix: version numbers from proto3 messages follow camel case. (#1868)
JoshuaSBrown Feb 23, 2026
af9c558
Merge branch 'devel' into 1854-DAPS-refactor-mock-core-server-proto3
JoshuaSBrown Feb 23, 2026
83482a5
docs: fix jsdoc error.
JoshuaSBrown Feb 23, 2026
fb65850
[DAPS-1862] - fix allocation change failure (#1864)
JoshuaSBrown Feb 24, 2026
874878c
Merge pull request #1879 from ORNL/staging
JoshuaSBrown Feb 25, 2026
edf9513
[DAPS-1854] - refactor mock core server to use proto3
JoshuaSBrown Mar 12, 2026
5afd3b6
[DAPS-1663] Adding LogContext to dbGetRaw, Correlation_ID to dbMainte…
megatnt1122 Mar 19, 2026
704536c
fix: mock_core server build (#1890)
JoshuaSBrown Mar 19, 2026
d8b0a38
[DAPS-1887] - refactor: facility fuse, remove dead code. (#1888)
JoshuaSBrown Mar 19, 2026
cda8509
[DAPS-1857] - feature: python client, tests, support schema functions…
JoshuaSBrown Mar 19, 2026
b995424
[DAPS-1855] - feature, core, add schema factory to decouple schema lo…
JoshuaSBrown Mar 19, 2026
7ccc58e
[DAPS-1896] - feature: foxx, schema format and type added to schema c…
JoshuaSBrown Mar 23, 2026
5d42565
[DAPS-1893] - feature: common, proto3 add fields for schema type, for…
JoshuaSBrown Mar 23, 2026
1dae067
[DAPS-1830-1] - core foxx refactored json schema integration 1 (#1892)
JoshuaSBrown Mar 24, 2026
c615fc7
[DAPS-1857-2] python client schema support (#1895)
JoshuaSBrown Mar 24, 2026
73ceb46
[DAPS-1830-2] - core foxx refactored json schema integration (#1899)
JoshuaSBrown Mar 24, 2026
d1c2505
[DAPS-1902] - core, common, feat: get schema api client into complian…
JoshuaSBrown Mar 25, 2026
0beb9a3
[DAPS-1906-1] - feature: add core unit testing to CI (#1907)
JoshuaSBrown Mar 25, 2026
635290e
[DAPS-1830-3] - core foxx refactored json schema integration 3 1827 (…
JoshuaSBrown Mar 25, 2026
0fb4652
[DAPS-1910] - upgrade: playwright 1.51.1 version. (#1911)
JoshuaSBrown Mar 26, 2026
3e602cc
[DAPS-1914] - bug, web schema id name version mismatch (#1915)
JoshuaSBrown Mar 27, 2026
73309fc
[DAPS-1913] - refactor: foxx ci test scripts consolidate database nam…
JoshuaSBrown Mar 27, 2026
ce563a8
[DAPS-1916] - tests add integration test for schema client handler to…
JoshuaSBrown Mar 28, 2026
a22d3a0
[DAPS-1912] - refactor: register linkml storage engine with schema ha…
JoshuaSBrown Mar 31, 2026
06fb561
[DAPS-1919] - python, web support linkml schema (#1921)
JoshuaSBrown Mar 31, 2026
6d2c062
[DAPS-1923] - update: version numbers bumped. (#1923)
JoshuaSBrown Mar 31, 2026
6ec5998
Merge branch 'staging' into devel
JoshuaSBrown Mar 31, 2026
920f1b8
[DAPS-1925] - fix: make version number id consistent. (#1926)
JoshuaSBrown Apr 1, 2026
4405555
Merge branch 'staging' into devel
JoshuaSBrown Apr 1, 2026
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
20 changes: 10 additions & 10 deletions cmake/Version.cmake
Original file line number Diff line number Diff line change
@@ -1,45 +1,45 @@


set(DATAFED_RELEASE_YEAR 2026)
set(DATAFED_RELEASE_MONTH 2)
set(DATAFED_RELEASE_DAY 24)
set(DATAFED_RELEASE_MONTH 3)
set(DATAFED_RELEASE_DAY 31)
set(DATAFED_RELEASE_HOUR 10)
set(DATAFED_RELEASE_MINUTE 0)

set(DATAFED_COMMON_LIB_MAJOR 3)
set(DATAFED_COMMON_LIB_MINOR 0)
set(DATAFED_COMMON_LIB_MINOR 1)
set(DATAFED_COMMON_LIB_PATCH 0)

set(DATAFED_COMMON_PROTOCOL_API_MAJOR 3)
set(DATAFED_COMMON_PROTOCOL_API_MINOR 0)
set(DATAFED_COMMON_PROTOCOL_API_MINOR 1)
set(DATAFED_COMMON_PROTOCOL_API_PATCH 0)

set(DATAFED_CORE_MAJOR 3)
set(DATAFED_CORE_MINOR 0)
set(DATAFED_CORE_MINOR 1)
set(DATAFED_CORE_PATCH 0)

set(DATAFED_FOXX_MAJOR 1)
set(DATAFED_FOXX_MINOR 3)
set(DATAFED_FOXX_MINOR 4)
set(DATAFED_FOXX_PATCH 0)

set(DATAFED_FOXX_API_MAJOR 1)
set(DATAFED_FOXX_API_MINOR 3)
set(DATAFED_FOXX_API_MINOR 4)
set(DATAFED_FOXX_API_PATCH 0)

set(DATAFED_WEB_MAJOR 3)
set(DATAFED_WEB_MINOR 0)
set(DATAFED_WEB_MINOR 1)
set(DATAFED_WEB_PATCH 0)

set(DATAFED_REPO_MAJOR 3)
set(DATAFED_REPO_MINOR 0)
set(DATAFED_REPO_MINOR 1)
set(DATAFED_REPO_PATCH 0)

set(DATAFED_AUTHZ_MAJOR 3)
set(DATAFED_AUTHZ_MINOR 0)
set(DATAFED_AUTHZ_PATCH 0)

set(DATAFED_PYTHON_CLIENT_MAJOR 5)
set(DATAFED_PYTHON_CLIENT_MINOR 0)
set(DATAFED_PYTHON_CLIENT_MINOR 1)
set(DATAFED_PYTHON_CLIENT_PATCH 0)
set(DATAFED_PYTHON_CLIENT_RELEASE_TYPE "")
set(DATAFED_PYTHON_CLIENT_PRE_RELEASE_IDENTIFER "")
4 changes: 2 additions & 2 deletions core/server/ClientWorker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@ ClientWorker::procRecordCreateRequest(const std::string &a_uid,

if (request->has_metadata() && request->has_sch_id()) {
validator_err = m_schema_handler->validateMetadataContent(
request->sch_id(), request->metadata(), log_context);
a_uid, request->sch_id(), request->metadata(), log_context);

if (!validator_err.empty()) {
DL_ERROR(log_context, "Metadata validation error: " << validator_err);
Expand Down Expand Up @@ -942,7 +942,7 @@ ClientWorker::procRecordUpdateRequest(const std::string &a_uid,

if (validator_err.empty()) {
validator_err = m_schema_handler->validateMetadataContent(
sch_id, effective_metadata, log_context);
a_uid, sch_id, effective_metadata, log_context);

if (!validator_err.empty()) {
DL_WARNING(log_context,
Expand Down
4 changes: 4 additions & 0 deletions core/server/Config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

// Core local private includes
#include "AuthenticationManager.hpp"
#include "SchemaAPIConfig.hpp"

// DataFed Common public includes
#include "common/DynaLog.hpp"
Expand All @@ -16,6 +17,7 @@
#include <mutex>
#include <stdint.h>
#include <string>
#include <unordered_map>

namespace SDMS {
namespace Core {
Expand Down Expand Up @@ -79,6 +81,8 @@ class Config {
uint32_t metrics_purge_period;
uint32_t metrics_purge_age;

std::unordered_map<std::string, SchemaAPIConfig> schemas;

// MsgComm::SecurityContext sec_ctx;
std::unique_ptr<ICredentials> sec_ctx;

Expand Down
38 changes: 30 additions & 8 deletions core/server/DatabaseAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2955,6 +2955,12 @@ void DatabaseAPI::schemaCreate(const SDMS::SchemaCreateRequest &a_request,
payload["pub"] = a_request.pub();
payload["sys"] = a_request.sys();
payload["def"] = a_request.def();
if (!a_request.format().empty()) {
payload["format"] = a_request.format();
}
if (!a_request.type().empty()) {
payload["type"] = a_request.type();
}
string body = payload.dump(-1, ' ', true);

dbPost("schema/create", {}, &body, result, log_context);
Expand Down Expand Up @@ -3058,29 +3064,45 @@ void DatabaseAPI::setSchemaData(SchemaData *a_schema,
a_schema->set_id(a_obj.getString("id"));
a_schema->set_ver(a_obj.getNumber("ver"));

if (a_obj.has("cnt"))
if (a_obj.has("cnt")) {
a_schema->set_cnt(a_obj.asNumber());
}

if (a_obj.has("own_id") && !a_obj.value().isNull())
if (a_obj.has("own_id") && !a_obj.value().isNull()) {
a_schema->set_own_id(a_obj.asString());
}

if (a_obj.has("own_nm") && !a_obj.value().isNull())
if (a_obj.has("own_nm") && !a_obj.value().isNull()) {
a_schema->set_own_nm(a_obj.asString());
}

if (a_obj.has("desc"))
if (a_obj.has("desc")) {
a_schema->set_desc(a_obj.asString());
}

if (a_obj.has("pub"))
if (a_obj.has("pub")) {
a_schema->set_pub(a_obj.asBool());
}

if (a_obj.has("depr"))
if (a_obj.has("depr")) {
a_schema->set_depr(a_obj.asBool());
}

if (a_obj.has("ref"))
if (a_obj.has("ref")) {
a_schema->set_ref(a_obj.asBool());
}

if (a_obj.has("def"))
if (a_obj.has("def")) {
a_schema->set_def(a_obj.value().toString());
}

if (a_obj.has("type")) {
a_schema->set_type(a_obj.asString());
}

if (a_obj.has("format")) {
a_schema->set_format(a_obj.asString());
}

Value::ArrayConstIter j;
SchemaData *dep;
Expand Down
25 changes: 23 additions & 2 deletions core/server/SchemaAPIClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -315,13 +315,34 @@ bool SchemaAPIClient::validateMetadata(const std::string &a_schema_id,
a_warnings = result.value("warnings", "");
return true;
}

if (code == 422) {
a_errors = result.value("message", "metadata validation failed");
if (result.contains("details") && result["details"].contains("errors")) {
std::string raw = result["details"]["errors"].get<std::string>();
if (a_engine == "linkml") {
// Normalize semicolon-separated errors to newline-separated
// for consistency with json-schema validator output
std::string normalized;
size_t pos = 0, found;
while ((found = raw.find("; ", pos)) != std::string::npos) {
normalized += raw.substr(pos, found - pos) + "\n";
pos = found + 2;
}
normalized += raw.substr(pos);
a_errors = normalized;
} else {
a_errors = raw;
}
} else {
a_errors = result.value("message", "metadata validation failed");
}
return false;
}
if (code == 404)

if (code == 404) {
EXCEPT_PARAM(BAD_REQUEST,
"SchemaAPI: schema " << a_schema_id << " not found");
}
EXCEPT_PARAM(SERVICE_ERROR,
"SchemaAPI validateMetadata failed, HTTP " << code);
}
Expand Down
54 changes: 45 additions & 9 deletions core/server/client_handlers/SchemaHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
#include "LocalJsonErrorHandler.hpp"
#include "common/TraceException.hpp"
#include "schema_storage/ArangoSchemaStorage.hpp"
#include "schema_storage/ExternalSchemaStorage.hpp"
#include "schema_validators/JsonSchemaValidator.hpp"
#include "schema_validators/ExternalSchemaValidator.hpp"
#include "Config.hpp"

// Standard includes
#include <functional>
Expand All @@ -13,8 +16,19 @@ using namespace std;
namespace SDMS {
namespace Core {

SchemaHandler::SchemaHandler(DatabaseAPI &a_db_client)
SchemaHandler::SchemaHandler(DatabaseAPI &a_db_client)
: m_db_client(a_db_client) {

if ( Config::getInstance().schemas.count("linkml") ){
SchemaAPIConfig linkml_config = Config::getInstance().schemas.at("linkml");
auto schema_api_client_storage = make_unique<SchemaAPIClient>(linkml_config);
auto linkml_storage = std::make_shared<ExternalSchemaStorage>(std::move(schema_api_client_storage));
m_schema_factory.registerStorage("linkml", std::move(linkml_storage));
auto schema_api_client_validator = make_unique<SchemaAPIClient>(linkml_config);
auto linkml_schema_validator = std::make_shared<ExternalSchemaValidator>(std::move(schema_api_client_validator), "linkml");
m_schema_factory.registerValidator("linkml",
std::move(linkml_schema_validator));
}
// Assumes that we have already placed the schema in the database, arango
// storage is a shell to be consistent with the interface.
auto arango_storage = std::make_shared<ArangoSchemaStorage>();
Expand All @@ -32,7 +46,7 @@ SchemaHandler::SchemaHandler(DatabaseAPI &a_db_client)
m_schema_factory.registerValidator("json-schema",
std::move(json_schema_validator));
m_schema_factory.setDefaultSchemaType("json-schema");
}
}

// ── Schema Definition Handlers ──────────────────────────────────────────────

Expand Down Expand Up @@ -62,7 +76,13 @@ void SchemaHandler::handleCreate(const std::string &a_uid,
}

// Persist to Arango — exception propagates naturally on failure
m_db_client.schemaCreate(a_request, a_reply, log_context);
// For external schema types, store a stub in Arango —
// the real content lives in external storage.
SchemaCreateRequest arango_req(a_request);
if (!a_request.type().empty() && a_request.type() != "json-schema") {
Comment on lines +79 to +82

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Storing {} as the schema definition for non-JSON engines may break consumers that rely on the DB’s def field.

Non-json-schema types now persist a stub ("{}") in def, with the actual content stored elsewhere. This preserves validation but changes the meaning of def for any code or tooling that reads SchemaData.def expecting the full schema. If such consumers exist, they will now see {} instead of the real definition. Consider either not setting def at all for external types (so clients can detect this) or defining a clear mechanism for clients to retrieve the full definition via the storage/validator layer rather than directly from the DB field.

arango_req.set_def("{}");
}
m_db_client.schemaCreate(arango_req, a_reply, log_context);

// Store content through factory (no-op for Arango, meaningful for external)
try {
Expand Down Expand Up @@ -140,7 +160,13 @@ void SchemaHandler::handleRevise(const std::string &a_uid,
}

// Create new revision in Arango
m_db_client.schemaRevise(a_request, a_reply, log_context);
// For external schema types, store a stub in Arango —
// the real content lives in external storage.
SchemaReviseRequest arango_req(a_request);
if (!schema_type.empty() && schema_type != "json-schema") {
arango_req.set_def("{}");
}
m_db_client.schemaRevise(arango_req, a_reply, log_context);

// Store content for new revision only when def was provided
if (a_request.has_def()) {
Expand Down Expand Up @@ -220,7 +246,13 @@ void SchemaHandler::handleUpdate(const std::string &a_uid,
}

// Update in-place in Arango
m_db_client.schemaUpdate(a_request, log_context);
// For external schema types, store a stub in Arango —
// the real content lives in external storage.
SchemaUpdateRequest arango_req(a_request);
if (!schema_type.empty() && schema_type != "json-schema") {
arango_req.set_def("{}");
}
m_db_client.schemaUpdate(arango_req, log_context);

// Update content in external storage only when def was provided.
// Uses updateContent (in-place) rather than storeContent (new entry).
Expand Down Expand Up @@ -250,7 +282,9 @@ void SchemaHandler::handleUpdate(const std::string &a_uid,
rollback_request.set_id(sch_doc.getString("id"));
rollback_request.set_desc(sch_doc.getString("desc"));
rollback_request.set_pub(sch_doc.getBool("pub"));
rollback_request.set_sys(sch_doc.getBool("sys"));
if (sch_doc.has("sys")) {
rollback_request.set_sys(sch_doc.getBool("sys"));
}

DL_ERROR(log_context,
"Schema storage update failed attempting rollback: " << e.what());
Expand All @@ -269,14 +303,17 @@ void SchemaHandler::handleUpdate(const std::string &a_uid,
// ── Metadata Validation ─────────────────────────────────────────────────────

std::string SchemaHandler::validateMetadataContent(
const std::string &a_uid,
const std::string &a_sch_id,
const std::string &a_metadata,
LogContext log_context) {

m_db_client.setClient(a_uid);
DL_DEBUG(log_context, "validateMetadataContent schema=" << a_sch_id);

std::string schema_type = "json-schema";
std::string schema_format = "json";
std::string metadata_format = "json";
std::string schema_def;

// Load schema record from DB
Expand Down Expand Up @@ -323,7 +360,7 @@ std::string SchemaHandler::validateMetadataContent(
}

auto result = validator.validateMetadata(
a_sch_id, schema_format, a_metadata, log_context);
a_sch_id, metadata_format, a_metadata, log_context);

if (!result.valid) {
return result.errors;
Expand All @@ -342,10 +379,9 @@ void SchemaHandler::handleMetadataValidate(
LogContext log_context) {

DL_DEBUG(log_context, "Metadata validate");
m_db_client.setClient(a_uid);

std::string errors = validateMetadataContent(
a_request.sch_id(), a_request.metadata(), log_context);
a_uid, a_request.sch_id(), a_request.metadata(), log_context);

if (!errors.empty()) {
a_reply.set_errors(errors);
Expand Down
7 changes: 4 additions & 3 deletions core/server/client_handlers/SchemaHandler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,10 @@ class SchemaHandler {
AckReply &a_reply,
LogContext log_context);

std::string validateMetadataContent(const std::string &a_sch_id,
const std::string &a_metadata,
LogContext log_context);
std::string validateMetadataContent(const std::string &a_uid,
const std::string &a_sch_id,
const std::string &a_metadata,
LogContext log_context);

private:
/**
Expand Down
18 changes: 18 additions & 0 deletions core/server/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

// Standard includes
#include "Config.hpp"
#include "SchemaAPIConfig.hpp"
#include <climits>
#include <fstream>
#include <iostream>
Expand All @@ -34,6 +35,7 @@ int main(int a_argc, char **a_argv) {
LogContext log_context;
log_context.thread_name = "core_server";
log_context.thread_id = 0;
Core::SchemaAPIConfig linkml_schema_config;

try {

Expand Down Expand Up @@ -64,6 +66,12 @@ int main(int a_argc, char **a_argv) {
"Globus authorization API base URL")(
"glob-xfr-url", po::value<string>(&config.glob_xfr_url),
"Globus transfer API base URL")(
"linkml-schema-api-url",
po::value<string>(&linkml_schema_config.base_url),
"External LinkML schema API base URL (enables LinkML support)")(
"linkml-schema-api-token",
po::value<string>(&linkml_schema_config.bearer_token),
"Bearer token for LinkML schema API authentication")(
"client-id", po::value<string>(&config.client_id), "Client ID")(
"client-secret", po::value<string>(&config.client_secret),
"Client secret")("task-purge-age",
Expand Down Expand Up @@ -152,6 +160,16 @@ int main(int a_argc, char **a_argv) {

return 0;
}

if (linkml_schema_config.isConfigured()) {
config.schemas["linkml"] = linkml_schema_config;
DL_INFO(log_context, "LinkML schema engine enabled, API: "
<< linkml_schema_config.base_url);
} else {
DL_INFO(log_context,
"LinkML schema engine disabled (no linkml-schema-api-url configured)");
}

if (cfg_log_level != UINT_MAX) {
if (cfg_log_level >=
static_cast<unsigned int>(LogLevel::LAST_SENTINEL)) {
Expand Down
Loading
Loading