From 674bb36329633245b35c7ccfd1dad97e337d4494 Mon Sep 17 00:00:00 2001 From: Gareth Sylvester-Bradley Date: Mon, 22 May 2023 12:24:38 +0100 Subject: [PATCH] More flexible default implementation of annotation_patch_merger --- .../nmos-cpp-node/node_implementation.cpp | 18 +++++++++--- Development/nmos/annotation_api.cpp | 29 +++++++++++++------ Development/nmos/annotation_api.h | 16 ++++++---- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 85df5bca5..b1c252891 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1270,13 +1270,23 @@ nmos::channelmapping_activation_handler make_node_implementation_channelmapping_ } // Example Annotation API patch callback to update resource labels, descriptions and tags -nmos::annotation_patch_merger make_node_implementation_annotation_patch_merger(slog::base_gate& gate) +nmos::annotation_patch_merger make_node_implementation_annotation_patch_merger(const nmos::settings& settings, slog::base_gate& gate) { - return [&gate](const nmos::resource& resource, web::json::value& value, const web::json::value& patch) + using web::json::value; + using web::json::value_of; + + return [&settings, &gate](const nmos::resource& resource, web::json::value& value, const web::json::value& patch) { const std::pair id_type{ resource.id, resource.type }; slog::log(gate, SLOG_FLF) << nmos::stash_category(impl::categories::node_implementation) << "Updating " << id_type; - nmos::details::merge_annotation_patch(value, patch); + // this example uses the specified tags for node and device resources as defaults + const auto default_tags + = id_type.second == nmos::types::node ? impl::fields::node_tags(settings) + : id_type.second == nmos::types::device ? impl::fields::device_tags(settings) + : value::object(); + // and uses the default predicate for read-only tags + nmos::details::merge_annotation_patch(value, patch, &nmos::details::is_read_only_tag, value_of({ { nmos::fields::tags, default_tags } })); + // this example does not save the new values to persistent storage or e.g. reject values that are too large }; } @@ -1434,5 +1444,5 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode .on_connection_activated(make_node_implementation_connection_activation_handler(model, gate)) .on_validate_channelmapping_output_map(make_node_implementation_map_validator()) // may be omitted if not required .on_channelmapping_activated(make_node_implementation_channelmapping_activation_handler(gate)) - .on_merge_annotation_patch(make_node_implementation_annotation_patch_merger(gate)); // may be omitted if not required + .on_merge_annotation_patch(make_node_implementation_annotation_patch_merger(model.settings, gate)); // may be omitted if not required } diff --git a/Development/nmos/annotation_api.cpp b/Development/nmos/annotation_api.cpp index 0cf638672..0f71a3e45 100644 --- a/Development/nmos/annotation_api.cpp +++ b/Development/nmos/annotation_api.cpp @@ -67,29 +67,35 @@ namespace nmos namespace details { + // BCP-002-01 Group Hint tag and BCP-002-02 Asset Distinguishing Information tags are read-only + // all other tags are read/write bool is_read_only_tag(const utility::string_t& key) { return boost::algorithm::starts_with(key, U("urn:x-nmos:tag:asset:")) || boost::algorithm::starts_with(key, U("urn:x-nmos:tag:grouphint/")); } - void merge_annotation_patch(web::json::value& value, const web::json::value& patch) + const web::json::field_as_string_or default_label{ nmos::fields::label.key, U("") }; + const web::json::field_as_string_or default_description{ nmos::fields::description.key, U("") }; + const web::json::field_as_value_or default_tags{ nmos::fields::tags.key, web::json::value::object() }; + + void merge_annotation_patch(web::json::value& value, const web::json::value& patch, annotation_tag_predicate is_read_only_tag, const web::json::value& default_value) { // reject changes to read-ony tags if (patch.has_object_field(nmos::fields::tags)) { const auto& tags = nmos::fields::tags(patch); - auto patch_readonly = std::find_if(tags.begin(), tags.end(), [](const std::pair& field) + auto patch_readonly = std::find_if(tags.begin(), tags.end(), [&](const std::pair& field) { return is_read_only_tag(field.first); }); if (tags.end() != patch_readonly) throw std::runtime_error("cannot patch read-only tag: " + utility::us2s(patch_readonly->first)); } - // save existing read-only tags + // save existing read-only tags (so that read-only tags don't need to be included in default_value) - auto readonly_tags = web::json::value_from_fields(nmos::fields::tags(value) | boost::adaptors::filtered([](const std::pair& field) + auto readonly_tags = web::json::value_from_fields(nmos::fields::tags(value) | boost::adaptors::filtered([&](const std::pair& field) { return is_read_only_tag(field.first); })); @@ -100,16 +106,21 @@ namespace nmos // apply defaults to properties that have been reset - web::json::insert(value, std::make_pair(nmos::fields::label, U(""))); - web::json::insert(value, std::make_pair(nmos::fields::description, U(""))); + web::json::insert(value, std::make_pair(nmos::fields::label, details::default_label(default_value))); + web::json::insert(value, std::make_pair(nmos::fields::description, details::default_description(default_value))); web::json::insert(value, std::make_pair(nmos::fields::tags, readonly_tags)); + auto& tags = value.at(nmos::fields::tags); + for (const auto& default_tag : details::default_tags(default_value).as_object()) + { + web::json::insert(tags, default_tag); + } } void assign_annotation_patch(web::json::value& value, web::json::value&& patch) { - if (value.has_string_field(nmos::fields::label)) value[nmos::fields::label] = std::move(patch.at(nmos::fields::label)); - if (value.has_string_field(nmos::fields::description)) value[nmos::fields::description] = std::move(patch.at(nmos::fields::description)); - if (value.has_object_field(nmos::fields::tags)) value[nmos::fields::tags] = std::move(patch.at(nmos::fields::tags)); + if (patch.has_string_field(nmos::fields::label)) value[nmos::fields::label] = std::move(patch.at(nmos::fields::label)); + if (patch.has_string_field(nmos::fields::description)) value[nmos::fields::description] = std::move(patch.at(nmos::fields::description)); + if (patch.has_object_field(nmos::fields::tags)) value[nmos::fields::tags] = std::move(patch.at(nmos::fields::tags)); } void handle_annotation_patch(nmos::resources& resources, const nmos::resource& resource, const web::json::value& patch, const nmos::annotation_patch_merger& merge_patch, slog::base_gate& gate) diff --git a/Development/nmos/annotation_api.h b/Development/nmos/annotation_api.h index c6586a8f4..53c881907 100644 --- a/Development/nmos/annotation_api.h +++ b/Development/nmos/annotation_api.h @@ -31,13 +31,19 @@ namespace nmos namespace details { - void merge_annotation_patch(web::json::value& value, const web::json::value& patch); + typedef std::function annotation_tag_predicate; + + // BCP-002-01 Group Hint tag and BCP-002-02 Asset Distinguishing Information tags are read-only + // all other tags are read/write + bool is_read_only_tag(const utility::string_t& name); + + // this function merges the patch into the value with few additional constraints + // when any fields are reset using null, default values are applied if specified or + // read-write tags are removed, and label and description are set to the empty string + void merge_annotation_patch(web::json::value& value, const web::json::value& patch, annotation_tag_predicate is_read_only_tag = &nmos::details::is_read_only_tag, const web::json::value& default_value = {}); } - // this function merges the patch into the value with few additional constraints - // i.e. label, description and all tags are read/write except Group Hint and Asset Distinguishing Information - // when reset using null, tags are removed, and label and description are set to the empty string - // (this is the default patch merger) + // this is the default patch merger inline void merge_annotation_patch(const nmos::resource& resource, web::json::value& value, const web::json::value& patch) { details::merge_annotation_patch(value, patch);