diff --git a/cypress/integration/validation.js b/cypress/integration/validation.js index e796fcb..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 @@ -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 53cc567..f097bbf 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 @@ -67,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('/'); @@ -196,6 +207,7 @@ function validateReview(fieldName, doc) { return validateThingOrId(fieldName, {"Review": reviewFieldValidators}, doc); } +// Define validators for each field of each type of Thing var softwareFieldValidators = { "@id": validateUrl, @@ -323,7 +335,6 @@ var personFieldValidators = { "url": validateUrls, }; - var organizationFieldValidators = { "@id": validateUrl, "id": validateUrl,