From df3c1ea3c812652cb9f774ecd2b9d191565c68a2 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 31 Mar 2026 14:17:37 -0400 Subject: [PATCH 1/7] refactor: gracefully handle linkml schema errors. --- core/server/SchemaAPIClient.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/core/server/SchemaAPIClient.cpp b/core/server/SchemaAPIClient.cpp index 933fefec5..2168d3bba 100644 --- a/core/server/SchemaAPIClient.cpp +++ b/core/server/SchemaAPIClient.cpp @@ -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(); + 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); } From bbf898c2737952a1aba2620bda48ffdd96fba617 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 31 Mar 2026 14:18:12 -0400 Subject: [PATCH 2/7] refactor: add linkml schemaservice configuration. --- core/server/main.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/server/main.cpp b/core/server/main.cpp index 48cca3b5b..b1f7a1545 100644 --- a/core/server/main.cpp +++ b/core/server/main.cpp @@ -14,6 +14,7 @@ // Standard includes #include "Config.hpp" +#include "SchemaAPIConfig.hpp" #include #include #include @@ -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 { @@ -64,6 +66,12 @@ int main(int a_argc, char **a_argv) { "Globus authorization API base URL")( "glob-xfr-url", po::value(&config.glob_xfr_url), "Globus transfer API base URL")( + "linkml-schema-api-url", + po::value(&linkml_schema_config.base_url), + "External LinkML schema API base URL (enables LinkML support)")( + "linkml-schema-api-token", + po::value(&linkml_schema_config.bearer_token), + "Bearer token for LinkML schema API authentication")( "client-id", po::value(&config.client_id), "Client ID")( "client-secret", po::value(&config.client_secret), "Client secret")("task-purge-age", @@ -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(LogLevel::LAST_SENTINEL)) { From 3eb338d02267844eecb49ed09a779c25cda4064b Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 31 Mar 2026 14:19:10 -0400 Subject: [PATCH 3/7] refactor: constrain metadata format to json because it is compatible with linkml. --- core/server/client_handlers/SchemaHandler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/server/client_handlers/SchemaHandler.cpp b/core/server/client_handlers/SchemaHandler.cpp index 1421cf097..4a7756f8a 100644 --- a/core/server/client_handlers/SchemaHandler.cpp +++ b/core/server/client_handlers/SchemaHandler.cpp @@ -313,6 +313,7 @@ std::string SchemaHandler::validateMetadataContent( 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 @@ -359,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; From 8cea7d39294ea109cc47b04bebcc419c8518fbba Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 31 Mar 2026 14:22:06 -0400 Subject: [PATCH 4/7] refactor: add configuration support for schema service in startup configuration scripts. --- scripts/generate_core_config.sh | 17 +++++++++++++++++ scripts/generate_datafed.sh | 21 +++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/scripts/generate_core_config.sh b/scripts/generate_core_config.sh index 04a62900e..cf924c0e9 100755 --- a/scripts/generate_core_config.sh +++ b/scripts/generate_core_config.sh @@ -95,6 +95,18 @@ else local_DATAFED_CORE_LOG_LEVEL=$(printenv DATAFED_CORE_LOG_LEVEL) fi +if [ -z "${DATAFED_LINKML_SCHEMA_API_URL}" ]; then + local_DATAFED_LINKML_SCHEMA_API_URL="" +else + local_DATAFED_LINKML_SCHEMA_API_URL=$(printenv DATAFED_LINKML_SCHEMA_API_URL) +fi + +if [ -z "${DATAFED_LINKML_SCHEMA_API_TOKEN}" ]; then + local_DATAFED_LINKML_SCHEMA_API_TOKEN="" +else + local_DATAFED_LINKML_SCHEMA_API_TOKEN=$(printenv DATAFED_LINKML_SCHEMA_API_TOKEN) +fi + VALID_ARGS=$(getopt -o ht:c:f:a:s:i:u:p --long 'help',threads-task:,cred-dir:,threads-client:,database-ip-address:,globus-secret:,globus-id:,database-user:,database-password: -- "$@") if [[ $? -ne 0 ]]; then exit 1 @@ -224,6 +236,11 @@ client-secret=$local_DATAFED_GLOBUS_APP_SECRET # 4 - Debug # 5 - Trace log-level=$local_DATAFED_CORE_LOG_LEVEL +# External LinkML Schema API service. +# If empty, LinkML schema engine is not registered. +linkml-schema-api-url=$local_DATAFED_LINKML_SCHEMA_API_URL +# Bearer token for LinkML Schema API authentication (optional) +linkml-schema-api-token=$local_DATAFED_LINKML_SCHEMA_API_TOKEN EOF echo diff --git a/scripts/generate_datafed.sh b/scripts/generate_datafed.sh index 65cb9af66..156cdf4a0 100755 --- a/scripts/generate_datafed.sh +++ b/scripts/generate_datafed.sh @@ -190,6 +190,18 @@ else local_DATAFED_GLOBUS_SUBSCRIPTION=$(printenv DATAFED_GLOBUS_SUBSCRIPTION) fi +if [ -z "${DATAFED_LINKML_SCHEMA_API_URL}" ]; then + local_DATAFED_LINKML_SCHEMA_API_URL="" +else + local_DATAFED_LINKML_SCHEMA_API_URL=$(printenv DATAFED_LINKML_SCHEMA_API_URL) +fi + +if [ -z "${DATAFED_LINKML_SCHEMA_API_TOKEN}" ]; then + local_DATAFED_LINKML_SCHEMA_API_TOKEN="" +else + local_DATAFED_LINKML_SCHEMA_API_TOKEN=$(printenv DATAFED_LINKML_SCHEMA_API_TOKEN) +fi + if [ ! -d "$PATH_TO_CONFIG_DIR" ]; then mkdir -p "$PATH_TO_CONFIG_DIR" fi @@ -336,4 +348,13 @@ export DATAFED_GLOBUS_ALLOWED_DOMAINS="$local_DATAFED_GLOBUS_ALLOWED_DOMAINS" export DATAFED_GLOBUS_CONTROL_PORT="$local_DATAFED_GLOBUS_CONTROL_PORT" # Globus subscription ID export DATAFED_GLOBUS_SUBSCRIPTION="${local_DATAFED_GLOBUS_SUBSCRIPTION}" + +# ************************************************ +# Env Variables for Schema API Service (LinkML) +# ************************************************ +# Base URL for the external schema API service (e.g. http://localhost:8080/v1) +# If empty, LinkML schema support is disabled. +export DATAFED_LINKML_SCHEMA_API_URL="$local_DATAFED_LINKML_SCHEMA_API_URL" +# Bearer token for Schema API authentication (optional) +export DATAFED_LINKML_SCHEMA_API_TOKEN="$local_DATAFED_LINKML_SCHEMA_API_TOKEN" EOF From f23f77308f0bdc2e8b839e80ad985b5b6fcf729d Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 31 Mar 2026 14:23:06 -0400 Subject: [PATCH 5/7] refactor: add support for schema format and type to python commands --- python/datafed_pkg/datafed/CLI.py | 51 +++++++++++++++++---- python/datafed_pkg/datafed/CommandLib.py | 58 ++++++++++++++---------- 2 files changed, 76 insertions(+), 33 deletions(-) diff --git a/python/datafed_pkg/datafed/CLI.py b/python/datafed_pkg/datafed/CLI.py index 6bb78f2b7..947d41d8a 100644 --- a/python/datafed_pkg/datafed/CLI.py +++ b/python/datafed_pkg/datafed/CLI.py @@ -2046,22 +2046,35 @@ def _schemaView(schema_id, resolve): @click.option( "-s", "--system", is_flag=True, required=False, help="Create as a system schema." ) +@click.option( + "-t", "--type", "schema_type", + type=click.Choice(["json-schema", "linkml"]), + default="json-schema", + help="Schema engine type (default: json-schema).", +) +@click.option( + "--format", "schema_format", + type=click.Choice(["json", "yaml"]), + default="json", + help="Schema definition format (default: json).", +) @_global_output_options -def _schemaCreate(schema_id, definition, definition_file, description, public, system): +def _schemaCreate(schema_id, definition, definition_file, description, + public, system, schema_type, schema_format): """ Create a new metadata schema. A JSON schema definition is required and may be provided inline via --definition or read from a file via --definition-file. Cannot specify both. """ - + if definition and definition_file: raise Exception( "Cannot specify both --definition and --definition-file options." ) - + if not definition and not definition_file: raise Exception("Must specify either --definition or --definition-file.") - + reply = _capi.schemaCreate( schema_id, definition=definition, @@ -2069,6 +2082,8 @@ def _schemaCreate(schema_id, definition, definition_file, description, public, s description=description, public=public, system=system, + schema_type=schema_type, + schema_format=schema_format, ) _generic_reply_handler(reply, _print_ack_reply) @@ -2106,19 +2121,26 @@ def _schemaCreate(schema_id, definition, definition_file, description, public, s required=False, help="Set as system schema.", ) +@click.option( + "-t", "--type", "schema_type", + type=click.Choice(["json-schema", "linkml"]), + default=None, required=False, + help="Schema engine type.", +) @_global_output_options -def _schemaRevise(schema_id, definition, definition_file, description, public, system): +def _schemaRevise(schema_id, definition, definition_file, description, + public, system, schema_type): """ Create a new revision of an existing schema. Any fields not provided are carried forward from the current revision. The definition may be provided inline or read from a file. """ - + if definition and definition_file: raise Exception( "Cannot specify both --definition and --definition-file options." ) - + reply = _capi.schemaRevise( schema_id, definition=definition, @@ -2126,6 +2148,7 @@ def _schemaRevise(schema_id, definition, definition_file, description, public, s description=description, public=public if public else None, system=system if system else None, + schema_type=schema_type, ) _generic_reply_handler(reply, _print_ack_reply) @@ -2164,20 +2187,27 @@ def _schemaRevise(schema_id, definition, definition_file, description, public, s required=False, help="Set as system schema.", ) +@click.option( + "-t", "--type", "schema_type", + type=click.Choice(["json-schema", "linkml"]), + default=None, required=False, + help="Schema engine type.", +) @_global_output_options def _schemaUpdate( - schema_id, new_id, definition, definition_file, description, public, system + schema_id, new_id, definition, definition_file, description, + public, system, schema_type ): """ Update an existing schema in place without creating a new revision. The definition may be provided inline or read from a file. """ - + if definition and definition_file: raise Exception( "Cannot specify both --definition and --definition-file options." ) - + reply = _capi.schemaUpdate( schema_id, new_id=new_id, @@ -2186,6 +2216,7 @@ def _schemaUpdate( description=description, public=public if public else None, system=system if system else None, + schema_type=schema_type, ) _generic_reply_handler(reply, _print_ack_reply) diff --git a/python/datafed_pkg/datafed/CommandLib.py b/python/datafed_pkg/datafed/CommandLib.py index da55665c3..2e543c8f6 100644 --- a/python/datafed_pkg/datafed/CommandLib.py +++ b/python/datafed_pkg/datafed/CommandLib.py @@ -336,34 +336,38 @@ def repoAllocationDelete(self, repo_id, subject): # are ever revised, renaming the field (e.g. to "definition") would # eliminate the need for this workaround. # ========================================================================= - def schemaCreate(self, schema_id, definition=None, definition_file=None, - description=None, public=False, system=False): + description=None, public=False, system=False, + schema_type="json-schema", schema_format="json"): """ Create a new metadata schema - Create a new metadata schema with a JSON schema definition. The - definition may be provided directly as a string, or read from a local - JSON file. Cannot specify both definition and definition_file. + Create a new metadata schema. The definition may be provided directly + as a string, or read from a local file. Cannot specify both definition + and definition_file. Parameters ---------- schema_id : str Schema ID definition : str, Optional. Default = None - JSON schema definition string + Schema definition string (JSON for json-schema, YAML for linkml) definition_file : str, Optional. Default = None - Path to local JSON file containing the schema definition + Path to local file containing the schema definition description : str, Optional. Default = None Text description of schema public : bool, Optional. Default = False Make schema publicly visible system : bool, Optional. Default = False Create as a system schema + schema_type : str, Optional. Default = "json-schema" + Schema engine type ("json-schema" or "linkml") + schema_format : str, Optional. Default = "json" + Schema format ("json" or "yaml") Returns ------- - msg : AckReply Google protobuf message + msg : SchemaDataReply Google protobuf message Response from DataFed Raises @@ -379,7 +383,8 @@ def schemaCreate(self, schema_id, definition=None, definition_file=None, if definition_file: definition = self._load_schema_file(definition_file) - self._validate_json(definition, "Schema definition") + if schema_type == "json-schema": + self._validate_json(definition, "Schema definition") msg = sdms.SchemaCreateRequest() @@ -387,8 +392,9 @@ def schemaCreate(self, schema_id, definition=None, definition_file=None, raise Exception("Colons are not allowed when creating a schema id.") msg.id = schema_id + ":0" - # See note above: "def" is a Python reserved keyword. setattr(msg, 'def', definition) + msg.type = schema_type + msg.format = schema_format if description: msg.desc = description @@ -402,32 +408,36 @@ def schemaCreate(self, schema_id, definition=None, definition_file=None, return self._mapi.sendRecv(msg) def schemaRevise(self, schema_id, definition=None, definition_file=None, - description=None, public=None, system=None): + description=None, public=None, system=None, + schema_type=None): """ Create a new revision of an existing schema Creates a new version of the specified schema. Any fields not provided are carried forward from the current revision. The definition may be - provided directly as a string or read from a local JSON file. + provided directly as a string or read from a local file. Parameters ---------- schema_id : str Schema ID of the schema to revise definition : str, Optional. Default = None - Updated JSON schema definition string + Updated schema definition string definition_file : str, Optional. Default = None - Path to local JSON file containing the updated schema definition + Path to local file containing the updated schema definition description : str, Optional. Default = None Updated text description public : bool, Optional. Default = None Update public visibility system : bool, Optional. Default = None Update system schema flag + schema_type : str, Optional. Default = None + Schema engine type. When None, JSON validation is applied for + backward compatibility. Set to "linkml" to skip JSON validation. Returns ------- - msg : AckReply Google protobuf message + msg : SchemaDataReply Google protobuf message Response from DataFed Raises @@ -440,7 +450,7 @@ def schemaRevise(self, schema_id, definition=None, definition_file=None, if definition_file: definition = self._load_schema_file(definition_file) - if definition is not None: + if definition is not None and (schema_type is None or schema_type == "json-schema"): self._validate_json(definition, "Schema definition") msg = sdms.SchemaReviseRequest() @@ -457,7 +467,6 @@ def schemaRevise(self, schema_id, definition=None, definition_file=None, msg.id = base + f":{ver}" if definition is not None: - # See schema section note: "def" is a Python reserved keyword. setattr(msg, 'def', definition) if description is not None: @@ -472,13 +481,14 @@ def schemaRevise(self, schema_id, definition=None, definition_file=None, return self._mapi.sendRecv(msg) def schemaUpdate(self, schema_id, new_id=None, definition=None, definition_file=None, - description=None, public=None, system=None): + description=None, public=None, system=None, + schema_type=None): """ Update an existing schema in place (no new revision) Modifies the current schema without creating a new version. The definition may be provided directly as a string or read from a local - JSON file. + file. Parameters ---------- @@ -487,15 +497,18 @@ def schemaUpdate(self, schema_id, new_id=None, definition=None, definition_file= new_id : str, Optional. Default = None New schema ID (rename) definition : str, Optional. Default = None - Updated JSON schema definition string + Updated schema definition string definition_file : str, Optional. Default = None - Path to local JSON file containing the updated schema definition + Path to local file containing the updated schema definition description : str, Optional. Default = None Updated text description public : bool, Optional. Default = None Update public visibility system : bool, Optional. Default = None Update system schema flag + schema_type : str, Optional. Default = None + Schema engine type. When None, JSON validation is applied for + backward compatibility. Set to "linkml" to skip JSON validation. Returns ------- @@ -512,7 +525,7 @@ def schemaUpdate(self, schema_id, new_id=None, definition=None, definition_file= if definition_file: definition = self._load_schema_file(definition_file) - if definition is not None: + if definition is not None and (schema_type is None or schema_type == "json-schema"): self._validate_json(definition, "Schema definition") msg = sdms.SchemaUpdateRequest() @@ -522,7 +535,6 @@ def schemaUpdate(self, schema_id, new_id=None, definition=None, definition_file= msg.id_new = new_id if definition is not None: - # See schema section note: "def" is a Python reserved keyword. setattr(msg, 'def', definition) if description is not None: From 0366887fa2eec3f17de1faa3c8d94a08d8f86322 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 31 Mar 2026 14:25:41 -0400 Subject: [PATCH 6/7] feat: add linkml support to web server. --- web/static/dlg_data_new_edit.js | 4 +- web/static/dlg_schema.js | 155 +++++++++++++++++++++++++++----- 2 files changed, 137 insertions(+), 22 deletions(-) diff --git a/web/static/dlg_data_new_edit.js b/web/static/dlg_data_new_edit.js index ef38d5d32..40abf6cbf 100644 --- a/web/static/dlg_data_new_edit.js +++ b/web/static/dlg_data_new_edit.js @@ -123,7 +123,9 @@ export function show(a_mode, a_data, a_parent, a_upd_perms, a_cb) { $("#sch_pick", frame).on("click", function (ev) { dlgSchList.show(true, false, function (schema) { - $("#sch_id", frame).val(schema.id + ":" + schema.ver); + var idx = schema.id.indexOf(":"); + var name = idx !== -1 ? schema.id.substring(0, idx) : schema.id; + $("#sch_id", frame).val(name + ":" + schema.ver); $("#md_err_msg", frame).text("").hide(); }); }); diff --git a/web/static/dlg_schema.js b/web/static/dlg_schema.js index da40f6807..a181cd888 100644 --- a/web/static/dlg_schema.js +++ b/web/static/dlg_schema.js @@ -31,6 +31,75 @@ function schemaId(id, ver) { return id.indexOf(":") !== -1 ? id : id + ":" + ver; } +/** + * Return the Ace editor mode string for a given schema type. + * @param {string} type - "json-schema" or "linkml" + * @returns {string} Ace mode path + */ +function aceMode(type) { + return type === "linkml" ? "ace/mode/yaml" : "ace/mode/json"; +} + +/** + * Return a human label for the definition tab header. + * @param {string} type - "json-schema" or "linkml" + * @returns {string} + */ +function defLabel(type) { + return type === "linkml" ? "Schema Definition (YAML):" : "Schema Definition (JSON):"; +} + +/** + * Return the schema format string for the API. + * @param {string} type - "json-schema" or "linkml" + * @returns {string} "json" or "yaml" + */ +function schemaFormat(type) { + return type === "linkml" ? "yaml" : "json"; +} + +/** + * Return a default template for a new schema definition. + * @param {string} type - "json-schema" or "linkml" + * @returns {string} + */ +function defaultTemplate(type) { + if (type === "linkml") { + return [ + "id: https://example.org/schemas/my_schema", + "name: my_schema", + "title: My Schema", + "description: Description of this schema", + "", + "prefixes:", + " linkml: https://w3id.org/linkml/", + "", + "default_range: string", + "", + "imports:", + " - linkml:types", + "", + "classes:", + " MyClass:", + " description: A sample class", + " attributes:", + " name:", + " required: true", + " value:", + " range: float", + ].join("\n"); + } + return JSON.stringify( + { + properties: { example: { type: "string" } }, + required: ["example"], + type: "object", + }, + null, + 4, + ); +} + export function show(a_mode, a_schema, a_cb) { var ele = document.createElement("div"); ele.id = "dlg_schema_" + (a_schema ? schemaName(a_schema.id) + "_" + a_schema.ver : "new"); @@ -40,6 +109,9 @@ export function show(a_mode, a_schema, a_cb) { json_val, schName = a_schema ? schemaName(a_schema.id) : null; + // Determine current schema type from existing schema or default + var curType = (a_schema && a_schema.type) ? a_schema.type : "json-schema"; + frame.html( "
\
    \ @@ -56,6 +128,12 @@ export function show(a_mode, a_schema, a_cb) { Uses:\ Refs:\ \ + Type:\ + \ + \ Owner:\ Access:\   \ @@ -72,7 +150,7 @@ export function show(a_mode, a_schema, a_cb) {
    \
    \
    \ - Schema Definition (JSON): editor help\ + " + defLabel(curType) + " editor help\
    \
    \
    \ @@ -92,12 +170,36 @@ export function show(a_mode, a_schema, a_cb) { var jsoned = ace.edit($("#sch_def", frame).get(0), { theme: settings.theme == "light" ? "ace/theme/light" : "ace/theme/dark", - mode: "ace/mode/json", + mode: aceMode(curType), fontSize: 16, autoScrollEditorIntoView: true, wrap: true, }); + // --- Type selector change handler --- + $("#sch_type", frame).on("change", function () { + var newType = $(this).val(); + var session = jsoned.getSession(); + + // Switch editor mode + session.setMode(aceMode(newType)); + + // Update definition tab label + $("#sch_def_label", frame).text(defLabel(newType)); + + // If the editor still has the default template (or is empty), + // replace it with the new type's template + var curVal = jsoned.getValue().trim(); + var jsonDefault = defaultTemplate("json-schema").trim(); + var linkmlDefault = defaultTemplate("linkml").trim(); + + if (!curVal || curVal === jsonDefault || curVal === linkmlDefault) { + jsoned.setValue(defaultTemplate(newType), -1); + } + + curType = newType; + }); + function handleSubmit(ok, reply) { if (ok) { if (a_cb) a_cb(); @@ -136,6 +238,7 @@ export function show(a_mode, a_schema, a_cb) { if (a_schema) { $("#sch_id", frame).val(schName); $("#sch_desc", frame).val(a_schema.desc); + $("#sch_type", frame).val(curType); if (a_mode == mode_rev) { $("#sch_ver", frame).val(a_schema.ver + 1); @@ -161,9 +264,17 @@ export function show(a_mode, a_schema, a_cb) { $("#sch_sys", frame).attr("checked", true); } - var def = JSON.parse(a_schema.def); - json_val = JSON.stringify(def, null, 4); - jsoned.setValue(json_val, -1); + // Display the definition based on type + if (curType === "linkml") { + // LinkML definitions are stored as YAML strings + json_val = a_schema.def; + jsoned.setValue(json_val, -1); + } else { + // JSON Schema definitions — pretty-print + var def = JSON.parse(a_schema.def); + json_val = JSON.stringify(def, null, 4); + jsoned.setValue(json_val, -1); + } if (a_mode != mode_rev) { var i, dep, html; @@ -190,24 +301,19 @@ export function show(a_mode, a_schema, a_cb) { $("#sch_cnt", frame).val(0); $("#sch_refs", frame).val(0); $("#sch_own", frame).val(settings.user.uid); - jsoned.setValue( - JSON.stringify( - { - properties: { example: { type: "string" } }, - required: ["example"], - type: "object", - }, - null, - 4, - ), - -1, - ); + $("#sch_type", frame).val("json-schema"); + jsoned.setValue(defaultTemplate("json-schema"), -1); } jsoned.resize(); util.inputDisable($("#sch_ver,#sch_cnt,#sch_refs,#sch_own", frame)); + // Type is immutable after creation — lock in view, edit, and revise modes + if (a_mode != mode_new) { + $("#sch_type", frame).prop("disabled", true); + } + if (a_mode == mode_view) { util.inputDisable($("#sch_id,#sch_desc", frame)); $(":radio:not(:checked)").attr("disabled", true); @@ -252,11 +358,16 @@ export function show(a_mode, a_schema, a_cb) { return; } - var anno = jsoned.getSession().getAnnotations(); + var selType = $("#sch_type", frame).val(); - if (anno && anno.length) { - dialogs.dlgAlert("Schema Error", "Schema has unresolved JSON syntax errors."); - return; + // Only check Ace annotations for JSON mode — YAML mode + // annotations are less reliable and not a hard gate + if (selType === "json-schema") { + var anno = jsoned.getSession().getAnnotations(); + if (anno && anno.length) { + dialogs.dlgAlert("Schema Error", "Schema has unresolved JSON syntax errors."); + return; + } } var obj = {}, @@ -276,6 +387,8 @@ export function show(a_mode, a_schema, a_cb) { if (a_mode == mode_new) { obj.id = $("#sch_id", frame).val().trim(); + obj.type = selType; + obj.format = schemaFormat(selType); console.log("new", obj); api.schemaCreate(obj, handleSubmit); From 2347335d17e4a960ad69fac1b15203501754edac Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 31 Mar 2026 18:34:52 +0000 Subject: [PATCH 7/7] chore: Auto-format JavaScript files with Prettier --- web/static/dlg_schema.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/static/dlg_schema.js b/web/static/dlg_schema.js index a181cd888..14b490f89 100644 --- a/web/static/dlg_schema.js +++ b/web/static/dlg_schema.js @@ -110,7 +110,7 @@ export function show(a_mode, a_schema, a_cb) { schName = a_schema ? schemaName(a_schema.id) : null; // Determine current schema type from existing schema or default - var curType = (a_schema && a_schema.type) ? a_schema.type : "json-schema"; + var curType = a_schema && a_schema.type ? a_schema.type : "json-schema"; frame.html( "
    \ @@ -150,7 +150,9 @@ export function show(a_mode, a_schema, a_cb) {
    \
    \
    \ - " + defLabel(curType) + " editor help\ + " + + defLabel(curType) + + " editor help\
    \
    \
    \