From 3f7a48e33260361413c87edc46d63673520e74b4 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Tue, 7 Oct 2025 15:47:43 +0100 Subject: [PATCH 1/3] Do not allow having both "id" and "@id" in Thing Signed-off-by: Arthit Suriyawongkul --- cypress/integration/validation.js | 38 ++++++++++++++++++++++++++++++- js/validation/index.js | 2 +- js/validation/things.js | 22 +++++++++++++----- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/cypress/integration/validation.js b/cypress/integration/validation.js index e796fcb..ef812be 100644 --- a/cypress/integration/validation.js +++ b/cypress/integration/validation.js @@ -105,7 +105,7 @@ describe('Document validation', function() { cy.get('#errorMessage').should('have.text', 'Unknown field "foobar".'); }); - it('errors when both "type" and "@type" are present', function() { + it('errors on having both "type" and "@type"', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", @@ -317,6 +317,42 @@ describe('Things or URLs validation', function() { cy.get('#errorMessage').should('have.text', '"license" must be an URL or a CreativeWork/SoftwareSourceCode/SoftwareApplication object, not: "Copyright 2021 Myself"'); }); + it('errors on having both "id" and "@id"', function () { + cy.get('#codemetaText').then((elem) => + elem.text(JSON.stringify({ + "@context": "https://doi.org/10.5063/schema/codemeta-2.0", + "@type": "SoftwareSourceCode", + "author": { + "id": "http://example.org/~jdoe", + "@id": "http://example.org/~jdoe", + "@type": "Person", + "name": "Jane Doe", + }, + })) + ); + cy.get('#validateCodemeta').click(); + + cy.get('#errorMessage').should('have.text', '"author" must use either "id" or "@id", not both.'); + }); + + it('errors on having both "type" and "@type"', function () { + cy.get('#codemetaText').then((elem) => + elem.text(JSON.stringify({ + "@context": "https://doi.org/10.5063/schema/codemeta-2.0", + "@type": "SoftwareSourceCode", + "author": { + "id": "http://example.org/~jdoe", + "type": "Person", + "@type": "Person", + "name": "Jane Doe", + }, + })) + ); + cy.get('#validateCodemeta').click(); + + cy.get('#errorMessage').should('have.text', '"author" must use either "type" or "@type", not both.'); + }); + it('errors on wrong type', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ diff --git a/js/validation/index.js b/js/validation/index.js index d9a284a..815a3b7 100644 --- a/js/validation/index.js +++ b/js/validation/index.js @@ -21,7 +21,7 @@ function validateDocument(doc) { // TODO: validate id/@id // Ensure either "type" or "@type" is used, but not both - const typeKeys = ['type', '@type']; + const typeKeys = ["type", "@type"]; if (typeKeys.filter(k => Object.prototype.hasOwnProperty.call(doc, k)).length > 1) { setError(`Document must use either "type" or "@type", not both.`); return false; diff --git a/js/validation/things.js b/js/validation/things.js index f252a24..3c4d751 100644 --- a/js/validation/things.js +++ b/js/validation/things.js @@ -10,7 +10,6 @@ */ function getDocumentType(doc) { - // TODO: check there is at most one. // FIXME: is the last variant allowed? return doc["type"] || doc["@type"] || doc["codemeta:type"] } @@ -68,8 +67,19 @@ function validateThingOrId(parentFieldName, typeFieldValidators, doc) { // // typeFieldValidators is a map: {type => {fieldName => fieldValidator}} function validateThing(parentFieldName, typeFieldValidators, doc) { - // TODO: check there is either id or @id but not both - // TODO: check there is either type or @type but not both + // Ensure either "id" or "@id" is used, but not both + const idKeys = ["id", "@id"]; + if (idKeys.filter(k => Object.prototype.hasOwnProperty.call(doc, k)).length > 1) { + setError(`"${parentFieldName}" must use either "id" or "@id", not both.`); + return false; + } + + // Ensure either "type" or "@type" is used, but not both + const typeKeys = ["type", "@type"]; + if (typeKeys.filter(k => Object.prototype.hasOwnProperty.call(doc, k)).length > 1) { + setError(`"${parentFieldName}" must use either "type" or "@type", not both.`); + return false; + } var acceptedTypesString = Object.keys(typeFieldValidators).join('/'); @@ -185,16 +195,16 @@ function validateActor(fieldName, doc) { // Validates a single Person object function validatePerson(fieldName, doc) { - return validateThingOrId(fieldName, {"Person": personFieldValidators}, doc); + return validateThingOrId(fieldName, { "Person": personFieldValidators }, doc); } // Validates a single Organization object function validateOrganization(fieldName, doc) { - return validateThingOrId(fieldName, {"Organization": organizationFieldValidators}, doc); + return validateThingOrId(fieldName, { "Organization": organizationFieldValidators }, doc); } function validateReview(fieldName, doc) { - return validateThingOrId(fieldName, {"Review": reviewFieldValidators}, doc); + return validateThingOrId(fieldName, { "Review": reviewFieldValidators }, doc); } From 985f18be8befeceb4461914171f0cc5dfcd7574f Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Tue, 7 Oct 2025 15:53:48 +0100 Subject: [PATCH 2/3] Add comment --- js/validation/things.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/validation/things.js b/js/validation/things.js index 3c4d751..bf75e3f 100644 --- a/js/validation/things.js +++ b/js/validation/things.js @@ -195,18 +195,19 @@ function validateActor(fieldName, doc) { // Validates a single Person object function validatePerson(fieldName, doc) { - return validateThingOrId(fieldName, { "Person": personFieldValidators }, doc); + return validateThingOrId(fieldName, {"Person": personFieldValidators}, doc); } // Validates a single Organization object function validateOrganization(fieldName, doc) { - return validateThingOrId(fieldName, { "Organization": organizationFieldValidators }, doc); + return validateThingOrId(fieldName, {"Organization": organizationFieldValidators}, doc); } function validateReview(fieldName, doc) { - return validateThingOrId(fieldName, { "Review": reviewFieldValidators }, doc); + return validateThingOrId(fieldName, {"Review": reviewFieldValidators}, doc); } +// Define validators for each field of each type of Thing var softwareFieldValidators = { "@id": validateUrl, @@ -334,7 +335,6 @@ var personFieldValidators = { "url": validateUrls, }; - var organizationFieldValidators = { "@id": validateUrl, "id": validateUrl, From 8de90f2f1635ad7f27bf85f442c4eca464c1661b Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Tue, 7 Oct 2025 16:10:13 +0100 Subject: [PATCH 3/3] Update year in copyright line --- cypress/integration/validation.js | 2 +- js/validation/things.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress/integration/validation.js b/cypress/integration/validation.js index ef812be..3670f89 100644 --- a/cypress/integration/validation.js +++ b/cypress/integration/validation.js @@ -1,5 +1,5 @@ /** - * Copyright (C) 2020-2021 The Software Heritage developers + * Copyright (C) 2020-2025 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information diff --git a/js/validation/things.js b/js/validation/things.js index bf75e3f..e340166 100644 --- a/js/validation/things.js +++ b/js/validation/things.js @@ -1,5 +1,5 @@ /** - * Copyright (C) 2020-2021 The Software Heritage developers + * Copyright (C) 2020-2025 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information