From 1108d90b0b48d32a282c097eff90c4aa651805d2 Mon Sep 17 00:00:00 2001 From: Oliver Woolland Date: Tue, 1 Sep 2026 16:06:30 +0100 Subject: [PATCH 1/3] Remove shared pointer behaviour --- README.md | 43 +++++++++++++------------------------------ include/ro-crate.hpp | 35 ++++++++++++++++++++--------------- tests/example.cpp | 5 +++-- tests/integration.cpp | 8 ++++---- tests/unit.cpp | 12 ------------ 5 files changed, 40 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 537b1d2..dbb6b58 100644 --- a/README.md +++ b/README.md @@ -166,50 +166,33 @@ relationships between entities. When an entity is created, it is not automatically added to the RO-Crate. The user must explicitly add the entity to the RO-Crate using the `addEntity` method. -When an entity is added to the RO-Crate, it is stored in a map of entities. +When an entity is added to the RO-Crate, a copy is stored in a map of entities. -For maximum flexiblity, an already added entity may still be updated on the -original entity object. The RO-Crate shares the same entity object, so any changes -made to the original entity will be reflected in the RO-Crate. - -Worked example: +Updates to the copy will not be reflected in the RO-Crate. If you want to update +an entity, you must retrieve it. See below for an example: ```cpp ROCrate crate; Entity alice({"Person"}); -alice.set("name", "Alice"); - crate.addEntity("#alice", alice); -/* Crate state: -{ - "#alice": { - "type": ["Person"], - "name": "Alice" - } -} -*/ - -// Add an additional property to the crate entity -Entity crateAlice = crate.getEntity("#alice"); -crateAlice.set("description", "One of hopefully many Contextual Entities"); +// Update the entity, use & to allow modifications +Entity& aliceCopy = crate.getEntity("#alice"); +aliceCopy.set("name", "Alice Smith"); -// Add an additional property to the original entity object -alice.set("occupation", "Software Engineer"); - -/* Crate state: +/* Output { - "#alice": { - "type": ["Person"], - "name": "Alice", - "description": "One of hopefully many Contextual Entities", - "occupation": "Software Engineer" - } + "@id": "#alice", + "@type": [ + "Person" + ], + "name": "Alice Smith" } */ ``` + ## Reference Reference documentation generated by [doxide](doxide.org) is available [here](http://esciencelab.org.uk/ro-crate-cpp/). diff --git a/include/ro-crate.hpp b/include/ro-crate.hpp index 3449cdc..2a5ef50 100644 --- a/include/ro-crate.hpp +++ b/include/ro-crate.hpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -79,11 +78,10 @@ namespace rocrate { * @throw std::invalid_argument if the id is empty. */ void assignId(const std::string& id); - std::shared_ptr properties_; + Properties properties_; }; - inline Entity::Entity(std::vector types) - : properties_(std::make_shared()) { + inline Entity::Entity(std::vector types) { // Validate types (reject empty) if ( types.empty() ) { @@ -94,7 +92,7 @@ namespace rocrate { std::vector typeValues; for (const auto& type : types) typeValues.push_back({type, ValueType::Literal}); - properties_->emplace("@type", typeValues); + properties_.emplace("@type", typeValues); } inline void Entity::set(Property property, Value value, ValueType valueType) { @@ -108,7 +106,7 @@ namespace rocrate { "Use ROCrate::addEntity to assign an ID."); // Add the value to the property in the properties map - (*properties_)[property].push_back({value, valueType}); + properties_[property].push_back({value, valueType}); } inline void Entity::set(Property property, const Entity& entity) { @@ -117,13 +115,20 @@ namespace rocrate { throw std::invalid_argument("Property name cannot be empty."); } - // Check if the entity has an '@id' property set - const auto id = entity.properties_->find("@id"); - if (id == entity.properties_->end() || id->second.empty()) - throw std::runtime_error("Entity does not have an '@id' property set."); + // Check if the entity has an '@id' property + auto it = entity.properties_.find("@id"); + if ( it == entity.properties_.end()) { + throw std::runtime_error("Referenced entity must have an '@id' property."); + } - // Add JSON reference to the entity's '@id' to the property in the properties map - set(property, id->second.front().value, ValueType::Reference); + // Get the ID and check it isn't empty + const auto& idValues = it->second; + if (idValues.empty()) { + throw std::runtime_error("Referenced entity must have a non-empty '@id' property."); + } + + // Add the reference to the property in the properties map + set(property, idValues[0].value, ValueType::Reference); } inline void Entity::assignId(const std::string& id) { @@ -133,7 +138,7 @@ namespace rocrate { } // Assign the '@id' property to the entity's properties map - (*properties_)["@id"] = {{id, ValueType::Literal}}; + properties_["@id"] = {{id, ValueType::Literal}}; } // --------------------------------------------------------------------------- @@ -213,7 +218,6 @@ namespace rocrate { "https://w3id.org/ro/crate/1.1", ValueType::Reference ); - addEntity("ro-crate-metadata.json", rootEntity); // Create the root dataset entity Entity datasetEntity({"Dataset"}); @@ -221,6 +225,7 @@ namespace rocrate { // Add the root dataset entity to the root metadata entity rootEntity.set("about", datasetEntity); + addEntity("ro-crate-metadata.json", rootEntity); } inline void ROCrate::addEntity(const std::string& id, Entity& entity) { @@ -301,7 +306,7 @@ namespace rocrate { nlohmann::json serialized; serialized["@id"] = id; - for (const auto& [property, values] : *entity.properties_) { + for (const auto& [property, values] : entity.properties_) { if (property == "@id") { continue; } diff --git a/tests/example.cpp b/tests/example.cpp index f59c820..a0697a0 100644 --- a/tests/example.cpp +++ b/tests/example.cpp @@ -17,11 +17,12 @@ void create_example_ro_crate() { // this is currently missing // Get the root metadata entity from the crate and set the description - Entity root = crate.getEntity("ro-crate-metadata.json"); + // Use & to ensure mutability + Entity& root = crate.getEntity("ro-crate-metadata.json"); root.set("description", "RO-Crate Metadata File Descriptor (this file)"); // Add name, description to the root data entity (./) - Entity rootData = crate.getEntity("./"); + Entity& rootData = crate.getEntity("./"); rootData.set("name", "Example RO-Crate"); rootData.set("description", "The RO-Crate Root Data Entity"); diff --git a/tests/integration.cpp b/tests/integration.cpp index b30b1fe..34464f9 100644 --- a/tests/integration.cpp +++ b/tests/integration.cpp @@ -12,7 +12,7 @@ TEST_CASE("Minimal RO-Crate", "[integration]") ROCrate crate; // Add metadata to root data entity - Entity rootData = crate.getEntity("./"); + Entity& rootData = crate.getEntity("./"); rootData.set("identifier", "https://doi.org/10.4225/59/59672c09f4a4b"); rootData.set("datePublished", "2017"); rootData.set("name", "Data files associated with the manuscript:Effects of facilitated family case conferencing for ..."); @@ -47,12 +47,12 @@ TEST_CASE("Example with file, author, location", "[integration]") ROCrate crate; // Add description to the root metadata entity (ro-crate-metadata.json) - Entity root = crate.getEntity("ro-crate-metadata.json"); + Entity& root = crate.getEntity("ro-crate-metadata.json"); root.set("description", "RO-Crate Metadata File Descriptor (this file)"); REQUIRE_NOTHROW(crate.getEntity("ro-crate-metadata.json")); // Add name, description to the root data entity (./) - Entity rootData = crate.getEntity("./"); + Entity& rootData = crate.getEntity("./"); rootData.set("name", "Example RO-Crate"); rootData.set("description", "The RO-Crate Root Data Entity"); REQUIRE_NOTHROW(crate.getEntity("./")); @@ -116,7 +116,7 @@ TEST_CASE("Example with web resources", "[integration]") crate.addEntity("https://zenodo.org/record/3541888/files/ro-crate-1.0.0.pdf", roCrateSpec); // Add to root data entity - Entity rootData = crate.getEntity("./"); + Entity& rootData = crate.getEntity("./"); rootData.set("hasPart", surveyResponses); rootData.set("hasPart", roCrateSpec); diff --git a/tests/unit.cpp b/tests/unit.cpp index c9b5d08..bea2832 100644 --- a/tests/unit.cpp +++ b/tests/unit.cpp @@ -100,18 +100,6 @@ TEST_CASE("RO-Crate initilises with valid root metadata and dataset entities", " REQUIRE_NOTHROW(crate.getEntity("./")); } -TEST_CASE("Crate and builder observe shared entity updates", "[unit]") { - rocrate::ROCrate crate; - Entity alice({"Person"}); - - crate.addEntity("#alice", alice); - - alice.set("name", "Alice"); - crate.getEntity("#alice").set("description", "Updated"); - - SUCCEED("No exceptions thrown, can't currently assert on entity values as they are not exposed in the API"); -} - TEST_CASE("Add entity rejects entities with duplicate IDs", "[unit]") { // Create a crate and add an entity rocrate::ROCrate crate; From 6a28b809ee186fc11ac96a045a66d19ce68e5e26 Mon Sep 17 00:00:00 2001 From: Oliver Woolland Date: Wed, 2 Sep 2026 10:39:04 +0100 Subject: [PATCH 2/3] Ensure example uses reference for got entities --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dbb6b58..0f2d1ad 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,11 @@ void create_example_ro_crate() { // this is currently missing // Get the root metadata entity from the crate and set the description - Entity root = crate.getEntity("ro-crate-metadata.json"); + Entity& root = crate.getEntity("ro-crate-metadata.json"); root.set("description", "RO-Crate Metadata File Descriptor (this file)"); // Add name, description to the root data entity (./) - Entity rootData = crate.getEntity("./"); + Entity& rootData = crate.getEntity("./"); rootData.set("name", "Example RO-Crate"); rootData.set("description", "The RO-Crate Root Data Entity"); From 92bc2a50562603ef287ae19d15ab56eda9b40fb9 Mon Sep 17 00:00:00 2001 From: Oliver Woolland Date: Wed, 2 Sep 2026 11:13:38 +0100 Subject: [PATCH 3/3] Better document get/set mechanisms --- README.md | 13 ++++++++----- include/ro-crate.hpp | 2 +- tests/example.cpp | 16 +++++++++------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 0f2d1ad..6dcc41d 100644 --- a/README.md +++ b/README.md @@ -32,15 +32,18 @@ void create_example_ro_crate() { // -------------------------------------------------------------------------- // Add description to the root metadata entity (ro-crate-metadata.json) as // this is currently missing + // + // Shows two methods for updating entities already in a crate A) and B) // Get the root metadata entity from the crate and set the description + // A) Get a reference to allow mutability Entity& root = crate.getEntity("ro-crate-metadata.json"); root.set("description", "RO-Crate Metadata File Descriptor (this file)"); // Add name, description to the root data entity (./) - Entity& rootData = crate.getEntity("./"); - rootData.set("name", "Example RO-Crate"); - rootData.set("description", "The RO-Crate Root Data Entity"); + // B) Directly edit in the crate + crate.getEntity("./").set("name", "Example RO-Crate"); + crate.getEntity("./").set("description", "The RO-Crate Root Data Entity"); // -------------------------------------------------------------------------- // Create the person, which is a contextual entity, and add it to the crate @@ -63,11 +66,11 @@ void create_example_ro_crate() { data1.set("author", alice); data1.set("contentLocation", catalinaPark); crate.addEntity("data1.txt", data1); - rootData.set("hasPart", data1); // Ensure that the root data entity has a hasPart relationship to data1 + crate.getEntity("./").set("hasPart", data1); // Ensure that the root data entity has a hasPart relationship to data1 Entity data2({"File"}); crate.addEntity("data2.txt", data2); - rootData.set("hasPart", data2); + crate.getEntity("./").set("hasPart", data2); // -------------------------------------------------------------------------- // Write out diff --git a/include/ro-crate.hpp b/include/ro-crate.hpp index 2a5ef50..c09ecd7 100644 --- a/include/ro-crate.hpp +++ b/include/ro-crate.hpp @@ -174,7 +174,7 @@ namespace rocrate { void addEntity(const std::string& id, Entity& entity); /** - * Retrieves an entity from the RO-Crate's entity register by its id. + * Retrieves an entity reference from the RO-Crate's entity register by its id. * * @param id The identifier of the entity to retrieve. * @return A reference to the entity with the specified id. diff --git a/tests/example.cpp b/tests/example.cpp index a0697a0..c4c003d 100644 --- a/tests/example.cpp +++ b/tests/example.cpp @@ -15,17 +15,19 @@ void create_example_ro_crate() { // -------------------------------------------------------------------------- // Add description to the root metadata entity (ro-crate-metadata.json) as // this is currently missing + // + // Shows two methods for updating entities already in a crate A) and B) // Get the root metadata entity from the crate and set the description - // Use & to ensure mutability + // A) Get a reference to allow mutability Entity& root = crate.getEntity("ro-crate-metadata.json"); root.set("description", "RO-Crate Metadata File Descriptor (this file)"); // Add name, description to the root data entity (./) - Entity& rootData = crate.getEntity("./"); - rootData.set("name", "Example RO-Crate"); - rootData.set("description", "The RO-Crate Root Data Entity"); - + // B) Directly edit in the crate + crate.getEntity("./").set("name", "Example RO-Crate"); + crate.getEntity("./").set("description", "The RO-Crate Root Data Entity"); + // -------------------------------------------------------------------------- // Create the person, which is a contextual entity, and add it to the crate @@ -47,11 +49,11 @@ void create_example_ro_crate() { data1.set("author", alice); data1.set("contentLocation", catalinaPark); crate.addEntity("data1.txt", data1); - rootData.set("hasPart", data1); // Ensure that the root data entity has a hasPart relationship to data1 + crate.getEntity("./").set("hasPart", data1); // Ensure that the root data entity has a hasPart relationship to data1 Entity data2({"File"}); crate.addEntity("data2.txt", data2); - rootData.set("hasPart", data2); + crate.getEntity("./").set("hasPart", data2); // -------------------------------------------------------------------------- // Write out