From 226dc59e83a40d78f9e4c2b5cc20832421ad0829 Mon Sep 17 00:00:00 2001 From: jonathan-r-thorpe <64410119+jonathan-r-thorpe@users.noreply.github.com> Date: Sun, 14 Jul 2024 12:13:17 +0100 Subject: [PATCH] IS-14 Refactor bulkProperties methods (#8) * Add Bulk Properties Manager to Device Model * Refactor bulkProperties GET method * Refactor bulkProperties PATCH and PUT methods * Updated according to latest specification --------- Co-authored-by: Simon Lo --- .../nmos-cpp-node/node_implementation.cpp | 8 +- Development/nmos/configuration_api.cpp | 197 +++++++++++++----- Development/nmos/configuration_api.h | 2 +- Development/nmos/control_protocol_handlers.h | 6 +- .../nmos/control_protocol_resource.cpp | 32 ++- Development/nmos/control_protocol_resource.h | 4 + Development/nmos/control_protocol_state.cpp | 29 +-- Development/nmos/control_protocol_typedefs.h | 30 +++ Development/nmos/json_fields.h | 3 +- Development/nmos/node_server.cpp | 2 +- 10 files changed, 236 insertions(+), 77 deletions(-) diff --git a/Development/nmos-cpp-node/node_implementation.cpp b/Development/nmos-cpp-node/node_implementation.cpp index 33a6251d..20d7767f 100644 --- a/Development/nmos-cpp-node/node_implementation.cpp +++ b/Development/nmos-cpp-node/node_implementation.cpp @@ -1287,6 +1287,8 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr nmos::push_back(root_block, class_manager); // add device-manager to root-block nmos::push_back(root_block, device_manager); + // add bulk-properties-manager to root-block + nmos::push_back(root_block, bulk_properties_manager); // insert control protocol resources to model insert_root_after(delay_millis, root_block, gate); @@ -1720,7 +1722,7 @@ nmos::control_protocol_property_changed_handler make_node_implementation_control // Example Device Configuration callback for creating a back-up dataset nmos::get_properties_by_path_handler make_node_implementation_get_properties_by_path_handler(const nmos::resources& resources, slog::base_gate& gate) { - return [&resources, &gate](const nmos::resource& resource, bool recurse) + return [&resources, &gate](nmos::get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, nmos::get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, const nmos::resource& resource, bool recurse) { slog::log(gate, SLOG_FLF) << nmos::stash_category(impl::categories::node_implementation) << "Do get_properties_by_path"; @@ -1732,7 +1734,7 @@ nmos::get_properties_by_path_handler make_node_implementation_get_properties_by_ // Example Device Configuration callback for validating a back-up dataset nmos::validate_set_properties_by_path_handler make_node_implementation_validate_set_properties_by_path_handler(const nmos::resources& resources, slog::base_gate& gate) { - return [&resources, &gate](const nmos::resource& resource, const web::json::value& backup_data_set, bool recurse) + return [&resources, &gate](nmos::get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, nmos::get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, const nmos::resource& resource, const web::json::value& backup_data_set, bool recurse, const web::json::array& included_property_traits) { slog::log(gate, SLOG_FLF) << nmos::stash_category(impl::categories::node_implementation) << "Do validate_set_properties_by_path"; @@ -1744,7 +1746,7 @@ nmos::validate_set_properties_by_path_handler make_node_implementation_validate_ // Example Device Configuration callback for restoring a back-up dataset nmos::set_properties_by_path_handler make_node_implementation_set_properties_by_path_handler(nmos::resources& resources, slog::base_gate& gate) { - return [&resources, &gate](const nmos::resource& resource, const web::json::value& data_set, bool recurse, bool allow_incomplete) + return [&resources, &gate](nmos::get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, nmos::get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, const nmos::resource& resource, const web::json::value& data_set, bool recurse, const web::json::array& included_property_traits) { slog::log(gate, SLOG_FLF) << nmos::stash_category(impl::categories::node_implementation) << "Do set_properties_by_path"; diff --git a/Development/nmos/configuration_api.cpp b/Development/nmos/configuration_api.cpp index 3875bab3..0bd66255 100644 --- a/Development/nmos/configuration_api.cpp +++ b/Development/nmos/configuration_api.cpp @@ -18,9 +18,9 @@ namespace nmos { - inline web::http::experimental::listener::api_router make_unmounted_configuration_api(node_model& model, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, get_properties_by_path_handler get_properties_by_path, validate_set_properties_by_path_handler validate_set_properties_by_path, set_properties_by_path_handler set_properties_by_path, control_protocol_property_changed_handler property_changed, slog::base_gate& gate); + inline web::http::experimental::listener::api_router make_unmounted_configuration_api(node_model& model, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, control_protocol_property_changed_handler property_changed, slog::base_gate& gate); - web::http::experimental::listener::api_router make_configuration_api(node_model& model, web::http::experimental::listener::route_handler validate_authorization, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, get_properties_by_path_handler get_properties_by_path, validate_set_properties_by_path_handler validate_set_properties_by_path, set_properties_by_path_handler set_properties_by_path, control_protocol_property_changed_handler property_changed, slog::base_gate& gate) + web::http::experimental::listener::api_router make_configuration_api(node_model& model, web::http::experimental::listener::route_handler validate_authorization, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, control_protocol_property_changed_handler property_changed, slog::base_gate& gate) { using namespace web::http::experimental::listener::api_router_using_declarations; @@ -51,7 +51,7 @@ namespace nmos return pplx::task_from_result(true); }); - configuration_api.mount(U("/x-nmos/") + nmos::patterns::configuration_api.pattern + U("/") + nmos::patterns::version.pattern, make_unmounted_configuration_api(model, get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor, get_properties_by_path, validate_set_properties_by_path, set_properties_by_path, property_changed, gate)); + configuration_api.mount(U("/x-nmos/") + nmos::patterns::configuration_api.pattern + U("/") + nmos::patterns::version.pattern, make_unmounted_configuration_api(model, get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor, property_changed, gate)); return configuration_api; } @@ -218,16 +218,16 @@ namespace nmos { web::json::value arguments = web::json::value_from_query(query); - if (arguments.has_boolean_field(fields::nc::recurse)) + if (arguments.has_field(fields::nc::recurse)) { - return fields::nc::recurse(arguments); + return U("false") != arguments.at(fields::nc::recurse).as_string(); } return true; } } - inline web::http::experimental::listener::api_router make_unmounted_configuration_api(node_model& model, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, get_properties_by_path_handler get_properties_by_path, validate_set_properties_by_path_handler validate_set_properties_by_path, set_properties_by_path_handler set_properties_by_path, control_protocol_property_changed_handler property_changed, slog::base_gate& gate_) + inline web::http::experimental::listener::api_router make_unmounted_configuration_api(node_model& model, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, control_protocol_property_changed_handler property_changed, slog::base_gate& gate_) { using namespace web::http::experimental::listener::api_router_using_declarations; @@ -647,122 +647,213 @@ namespace nmos }); }); - configuration_api.support(U("/rolePaths/") + nmos::patterns::rolePath.pattern + U("/bulkProperties/?"), methods::GET, [&model, get_properties_by_path, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + configuration_api.support(U("/rolePaths/") + nmos::patterns::rolePath.pattern + U("/bulkProperties/?"), methods::GET, [&model, get_control_protocol_method_descriptor, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { const auto role_path = parameters.at(nmos::patterns::rolePath.name); auto lock = model.read_lock(); - auto& resources = model.control_protocol_resources; - const auto& resource = details::find_resource(resources, role_path); - if (resources.end() != resource) + const auto& bulk_properties_manager = details::find_resource(resources, nmos::bulk_properties_manager_role); + + if (resources.end() != resource && resources.end() != bulk_properties_manager) { - bool recurse = details::parse_recurse_query_parameter(req.request_uri().query()); + auto method = get_control_protocol_method_descriptor(nc_bulk_properties_manager_class_id, nc_bulk_properties_manager_get_properties_by_path_method_id); + auto& nc_method_descriptor = method.first; + auto& control_method_handler = method.second; + web::http::status_code code{ status_codes::BadRequest }; + value method_result; - auto result = details::make_nc_method_result_error({ nc_method_status::method_not_implemented }, U("get_properties_by_path not provided")); - if (get_properties_by_path) + if (control_method_handler) { - result = get_properties_by_path(*resource, recurse); + try + { + bool recurse = details::parse_recurse_query_parameter(req.request_uri().query()); + + method_result = control_method_handler(resources, *resource, value_of({ { nmos::fields::nc::recurse, recurse } }), nmos::fields::nc::is_deprecated(nc_method_descriptor), gate_); + + auto status = nmos::fields::nc::status(method_result); + if (nc_method_status::ok == status || nc_method_status::method_deprecated == status) { code = status_codes::OK; } + else if (nc_method_status::parameter_error == status) { code = status_codes::BadRequest; } + else if (nc_method_status::device_error == status) { code = status_codes::InternalError; } + else { code = status_codes::InternalError; } + } + catch (const nmos::control_protocol_exception& e) + { + // invalid arguments + utility::stringstream_t ss; + ss << U("parameter error: ") << e.what(); + method_result = details::make_nc_method_result_error({ nmos::nc_method_status::parameter_error }, ss.str()); + + code = status_codes::BadRequest; + } } + else + { + // unknown methodId + method_result = details::make_nc_method_result_error({ nmos::nc_method_status::method_not_implemented }, U("get_properties_by_path unsupported by bulk properties manager.")); - auto status = nmos::fields::nc::status(result); - auto code = (nc_method_status::ok == status || nc_method_status::property_deprecated == status) ? status_codes::OK : status_codes::InternalError; - set_reply(res, code, result); + code = status_codes::NotFound; + } + set_reply(res, code, method_result); } else { - // resource not found for the role path - set_error_reply(res, status_codes::NotFound, U("Not Found; ") + role_path); + if (resources.end() == bulk_properties_manager) + { + // no bulk properties manager + set_error_reply(res, status_codes::NotFound, U("Bulk Properties Manager not found at ") + nmos::bulk_properties_manager_role); + } + else + { + // resource not found for the role path + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + role_path); + } } return pplx::task_from_result(true); }); - configuration_api.support(U("/rolePaths/") + nmos::patterns::rolePath.pattern + U("/bulkProperties/?"), methods::PATCH, [&model, validate_set_properties_by_path, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + configuration_api.support(U("/rolePaths/") + nmos::patterns::rolePath.pattern + U("/bulkProperties/?"), methods::PATCH, [&model, get_control_protocol_method_descriptor, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { const auto role_path = parameters.at(nmos::patterns::rolePath.name); + const nmos::api_version version = nmos::parse_api_version(parameters.at(nmos::patterns::version.name)); auto lock = model.read_lock(); - const nmos::api_version version = nmos::parse_api_version(parameters.at(nmos::patterns::version.name)); auto& resources = model.control_protocol_resources; - const auto& resource = details::find_resource(resources, role_path); - if (resources.end() != resource) + const auto& bulk_properties_manager = details::find_resource(resources, nmos::bulk_properties_manager_role); + if (resources.end() != resource && resources.end() != bulk_properties_manager) { - return details::extract_json(req, gate_).then([res, resources, resource, validate_set_properties_by_path, version, &gate_](value body) mutable + return details::extract_json(req, gate_).then([res, resources, resource, get_control_protocol_method_descriptor, version, &gate_](value body) mutable { // Validate JSON syntax according to the schema details::configurationapi_validator().validate(body, experimental::make_configurationapi_bulkProperties_validate_request_schema_uri(version)); - bool recurse = nmos::fields::nc::recurse(body); - const auto& data_set = nmos::fields::nc::data_set(body); - if (!data_set.is_null()) + auto method = get_control_protocol_method_descriptor(nc_bulk_properties_manager_class_id, nc_bulk_properties_manager_validate_set_properties_by_path_method_id); + auto& nc_method_descriptor = method.first; + auto& control_method_handler = method.second; + web::http::status_code code{ status_codes::BadRequest }; + value method_result; + + if (control_method_handler) { - auto result = details::make_nc_method_result_error({ nc_method_status::method_not_implemented }, U("validate_set_properties_by_path not provided")); - if (validate_set_properties_by_path) + try { - result = validate_set_properties_by_path(*resource, data_set, recurse); + method_result = control_method_handler(resources, *resource, nmos::fields::nc::arguments(body), nmos::fields::nc::is_deprecated(nc_method_descriptor), gate_); + + auto status = nmos::fields::nc::status(method_result); + if (nc_method_status::ok == status || nc_method_status::method_deprecated == status) { code = status_codes::OK; } + else if (nc_method_status::parameter_error == status) { code = status_codes::BadRequest; } + else if (nc_method_status::device_error == status) { code = status_codes::InternalError; } + else { code = status_codes::InternalError; } } + catch (const nmos::control_protocol_exception& e) + { + // invalid arguments + utility::stringstream_t ss; + ss << U("parameter error: ") << e.what(); + method_result = details::make_nc_method_result_error({ nmos::nc_method_status::parameter_error }, ss.str()); - auto status = nmos::fields::nc::status(result); - auto code = (nc_method_status::ok == status || nc_method_status::property_deprecated == status) ? status_codes::OK : status_codes::InternalError; - set_reply(res, code, result); + code = status_codes::BadRequest; + } } else { - set_reply(res, status_codes::BadRequest, nmos::details::make_nc_method_result_error({ nc_method_status::parameter_error }, U("Null dataSet parameter"))); + // unknown methodId + method_result = details::make_nc_method_result_error({ nmos::nc_method_status::method_not_implemented }, U("validate_set_properties_by_path unsupported by bulk properties manager.")); + + code = status_codes::NotFound; } + set_reply(res, code, method_result); + return true; }); } else { - // resource not found for the role path - set_error_reply(res, status_codes::NotFound, U("Not Found; ") + role_path); + if (resources.end() == bulk_properties_manager) + { + // no bulk properties manager + set_error_reply(res, status_codes::NotFound, U("Bulk Properties Manager not found at ") + nmos::bulk_properties_manager_role); + } + else + { + // resource not found for the role path + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + role_path); + } } return pplx::task_from_result(true); }); - configuration_api.support(U("/rolePaths/") + nmos::patterns::rolePath.pattern + U("/bulkProperties/?"), methods::PUT, [&model, set_properties_by_path, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) + configuration_api.support(U("/rolePaths/") + nmos::patterns::rolePath.pattern + U("/bulkProperties/?"), methods::PUT, [&model, get_control_protocol_method_descriptor, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters) { const auto role_path = parameters.at(nmos::patterns::rolePath.name); + const nmos::api_version version = nmos::parse_api_version(parameters.at(nmos::patterns::version.name)); auto lock = model.read_lock(); - const nmos::api_version version = nmos::parse_api_version(parameters.at(nmos::patterns::version.name)); auto& resources = model.control_protocol_resources; - const auto& resource = details::find_resource(resources, role_path); - if (resources.end() != resource) + const auto& bulk_properties_manager = details::find_resource(resources, nmos::bulk_properties_manager_role); + if (resources.end() != resource && resources.end() != bulk_properties_manager) { - return details::extract_json(req, gate_).then([res, resources, resource, set_properties_by_path, version, &gate_](value body) mutable + return details::extract_json(req, gate_).then([res, resources, resource, get_control_protocol_method_descriptor, version, &gate_](value body) mutable { // Validate JSON syntax according to the schema details::configurationapi_validator().validate(body, experimental::make_configurationapi_bulkProperties_set_request_schema_uri(version)); - const auto& arguments = nmos::fields::nc::arguments(body); - bool recurse = nmos::fields::nc::recurse(arguments); - bool allow_incomplete = nmos::fields::nc::allow_incomplete(arguments); - const auto& data_set = nmos::fields::nc::data_set(arguments); + auto method = get_control_protocol_method_descriptor(nc_bulk_properties_manager_class_id, nc_bulk_properties_manager_set_properties_by_path_method_id); + auto& nc_method_descriptor = method.first; + auto& control_method_handler = method.second; + web::http::status_code code{ status_codes::BadRequest }; + value method_result; - auto result = details::make_nc_method_result_error({ nc_method_status::method_not_implemented }, U("set_properties_by_path not provided")); - if (set_properties_by_path) + if (control_method_handler) { - result = set_properties_by_path(*resource, data_set, recurse, allow_incomplete); + try + { + method_result = control_method_handler(resources, *resource, nmos::fields::nc::arguments(body), nmos::fields::nc::is_deprecated(nc_method_descriptor), gate_); + auto status = nmos::fields::nc::status(method_result); + if (nc_method_status::ok == status || nc_method_status::method_deprecated == status) { code = status_codes::OK; } + else if (nc_method_status::parameter_error == status) { code = status_codes::BadRequest; } + else if (nc_method_status::device_error == status) { code = status_codes::InternalError; } + else { code = status_codes::InternalError; } + } + catch (const nmos::control_protocol_exception& e) + { + // invalid arguments + utility::stringstream_t ss; + ss << U("parameter error: ") << e.what(); + method_result = details::make_nc_method_result_error({ nmos::nc_method_status::parameter_error }, ss.str()); + + code = status_codes::BadRequest; + } } + else + { + // unknown methodId + method_result = details::make_nc_method_result_error({ nmos::nc_method_status::method_not_implemented }, U("set_properties_by_path unsupported by bulk properties manager.")); - auto status = nmos::fields::nc::status(result); - auto code = (nc_method_status::ok == status || nc_method_status::property_deprecated == status) ? status_codes::OK : status_codes::InternalError; - set_reply(res, code, result); + code = status_codes::NotFound; + } + set_reply(res, code, method_result); return true; }); } else { - // resource not found for the role path - set_error_reply(res, status_codes::NotFound, U("Not Found; ") + role_path); + if (resources.end() == bulk_properties_manager) + { + // no bulk properties manager + set_error_reply(res, status_codes::NotFound, U("Bulk Properties Manager not found at ") + nmos::bulk_properties_manager_role); + } + else + { + // resource not found for the role path + set_error_reply(res, status_codes::NotFound, U("Not Found; ") + role_path); + } } return pplx::task_from_result(true); diff --git a/Development/nmos/configuration_api.h b/Development/nmos/configuration_api.h index 6e4b8f08..9b1a7329 100644 --- a/Development/nmos/configuration_api.h +++ b/Development/nmos/configuration_api.h @@ -15,7 +15,7 @@ namespace nmos { struct node_model; - web::http::experimental::listener::api_router make_configuration_api(nmos::node_model& model, web::http::experimental::listener::route_handler validate_authorization, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, get_properties_by_path_handler get_properties_by_path, validate_set_properties_by_path_handler validate_set_properties_by_path, set_properties_by_path_handler set_properties_by_path, control_protocol_property_changed_handler property_changed, slog::base_gate& gate); + web::http::experimental::listener::api_router make_configuration_api(nmos::node_model& model, web::http::experimental::listener::route_handler validate_authorization, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, control_protocol_property_changed_handler property_changed, slog::base_gate& gate); } #endif diff --git a/Development/nmos/control_protocol_handlers.h b/Development/nmos/control_protocol_handlers.h index e7d5fda0..d57972b2 100644 --- a/Development/nmos/control_protocol_handlers.h +++ b/Development/nmos/control_protocol_handlers.h @@ -38,9 +38,9 @@ namespace nmos // Device Configuration handlers // these callbacks should not throw exceptions - typedef std::function get_properties_by_path_handler; - typedef std::function validate_set_properties_by_path_handler; - typedef std::function set_properties_by_path_handler; + typedef std::function get_properties_by_path_handler; + typedef std::function validate_set_properties_by_path_handler; + typedef std::function set_properties_by_path_handler; namespace experimental { diff --git a/Development/nmos/control_protocol_resource.cpp b/Development/nmos/control_protocol_resource.cpp index 9c16334c..ccbecdb7 100644 --- a/Development/nmos/control_protocol_resource.cpp +++ b/Development/nmos/control_protocol_resource.cpp @@ -1262,6 +1262,7 @@ namespace nmos web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("The values offered (this may include read-only values and also paths which are not the target role path)"), nmos::fields::nc::data_set, U("NcBulkValuesHolder"), false, false, value::null())); web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("If set the descriptor would contain all inherited elements"), nmos::fields::nc::path, U("NcRolePath"), false, false, value::null())); web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("If true will validate properties on target path and all the nested paths"), nmos::fields::nc::recurse, U("NcBoolean"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("If populated (not an empty collection) will include the properties matching any of the specified traits in the restore validation. When not populated only properties without traits are validated for restore"), nmos::fields::nc::included_property_traits, U("NcPropertyTrait"), false, true, value::null())); web::json::push_back(methods, details::make_nc_method_descriptor(U("Validate bulk properties for setting by given paths"), nc_bulk_properties_manager_validate_set_properties_by_path_method_id, U("ValidateSetPropertiesByPath"), U("NcMethodResultObjectPropertiesSetValidation"), parameters, false)); } { @@ -1269,7 +1270,7 @@ namespace nmos web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("The values offered (this may include read-only values and also paths which are not the target role path)"), nmos::fields::nc::data_set, U("NcBulkValuesHolder"), false, false, value::null())); web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("If set the descriptor would contain all inherited elements"), nmos::fields::nc::path, U("NcRolePath"), false, false, value::null())); web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("If true will validate properties on target path and all the nested paths"), nmos::fields::nc::recurse, U("NcBoolean"), false, false, value::null())); - web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("If true will allow the device to restore only the role paths which pass validation(perform an incomplete restore)"), nmos::fields::nc::allow_incomplete, U("NcBoolean"), false, false, value::null())); + web::json::push_back(parameters, details::make_nc_parameter_descriptor(U("If populated (not an empty collection) will include the properties matching any of the specified traits in the restore validation. When not populated only properties without traits are validated for restore"), nmos::fields::nc::included_property_traits, U("NcPropertyTrait"), false, true, value::null())); web::json::push_back(methods, details::make_nc_method_descriptor(U("Set bulk properties for setting by given paths"), nc_bulk_properties_manager_set_properties_by_path_method_id, U("SetPropertiesByPath"), U("NcMethodResultObjectPropertiesSetValidation"), parameters, false)); } @@ -2116,6 +2117,18 @@ namespace nmos // Device Configuration datatypes // TODO: add link + web::json::value make_nc_property_trait_datatype() + { + using web::json::value; + + auto items = value::array(); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Property is instance specific"), U("InstanceSpecific"), 1)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Property is ephemeral"), U("Ephemeral"), 2)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Property is immutable"), U("Immutable"), 3)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Property value is generated by the device"), U("DeviceGenerated"), 4)); + return details::make_nc_datatype_descriptor_enum(U("Property trait enumeration"), U("NcPropertyTrait"), items, value::null()); + } + // TODO: add link web::json::value make_nc_property_value_holder_datatype() { using web::json::value; @@ -2125,6 +2138,7 @@ namespace nmos web::json::push_back(fields, details::make_nc_field_descriptor(U("Property name"), nmos::fields::nc::name, U("NcString"), false, false, value::null())); web::json::push_back(fields, details::make_nc_field_descriptor(U("Property type name. If null it means the type is any"), nmos::fields::nc::type_name, U("NcName"), true, false, value::null())); web::json::push_back(fields, details::make_nc_field_descriptor(U("Is the property ReadOnly?"), nmos::fields::nc::is_read_only, U("NcBoolean"), true, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Describes the property traits as a collection of unique items"), nmos::fields::nc::traits, U("NcPropertyTrait"), false, true, value::null())); web::json::push_back(fields, details::make_nc_field_descriptor(U("Property value"), nmos::fields::nc::value, true, false, value::null())); return details::make_nc_datatype_descriptor_struct(U("Property value holder descriptor"), U("NcPropertyValueHolder"), fields, value::null()); @@ -2152,13 +2166,27 @@ namespace nmos return details::make_nc_datatype_descriptor_struct(U("Bulk values holder descriptor"), U("NcBulkValuesHolder"), fields, value::null()); } // TODO: add link + web::json::value make_nc_restore_validation_status_datatype() + { + using web::json::value; + + auto items = value::array(); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Restore was successful"), U("Ok"), 200)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Excluded from restore due to data provided in the request"), U("Excluded"), 204)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Restore failed because relevant backup data set provided is invalid"), U("InvalidData"), 400)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Restore failed because the role path is not found in the device model or the device cannot create the role path from the data set"), U("NotFound"), 404)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Restore failed because of missing dependency information in the relevant backup data set"), U("MissingDependency"), 424)); + web::json::push_back(items, details::make_nc_enum_item_descriptor(U("Restore failed due to an internal device error preventing the restore from happening"), U("DeviceError"), 500)); + return details::make_nc_datatype_descriptor_enum(U("Restore validation status enumeration"), U("NcRestoreValidationStatus"), items, value::null()); + } + // TODO: add link web::json::value make_nc_object_properties_set_validation_datatype() { using web::json::value; auto fields = value::array(); web::json::push_back(fields, details::make_nc_field_descriptor(U("Object role path"), nmos::fields::nc::path, U("NcRolePath"), false, false, value::null())); - web::json::push_back(fields, details::make_nc_field_descriptor(U("Validation status"), nmos::fields::nc::status, U("NcMethodStatus"), false, false, value::null())); + web::json::push_back(fields, details::make_nc_field_descriptor(U("Validation status"), nmos::fields::nc::status, U("NcRestoreValidationStatus"), false, false, value::null())); web::json::push_back(fields, details::make_nc_field_descriptor(U("Validation status message"), nmos::fields::nc::status_message, U("NcString"), true, false, value::null())); return details::make_nc_datatype_descriptor_struct(U("Object properties set validation descriptor"), U("NcObjectPropertiesSetValidation"), fields, value::null()); diff --git a/Development/nmos/control_protocol_resource.h b/Development/nmos/control_protocol_resource.h index 37948dc4..8336b62f 100644 --- a/Development/nmos/control_protocol_resource.h +++ b/Development/nmos/control_protocol_resource.h @@ -441,12 +441,16 @@ namespace nmos // Device configuration feature set datatypes // TODO: add link // + web::json::value make_nc_property_trait_datatype(); + // web::json::value make_nc_property_value_holder_datatype(); // web::json::value make_nc_object_properties_holder_datatype(); // web::json::value make_nc_bulk_values_holder_datatype(); // + web::json::value make_nc_restore_validation_status_datatype(); + // web::json::value make_nc_object_properties_set_validation_datatype(); // web::json::value make_nc_method_result_bulk_values_holder_datatype(); diff --git a/Development/nmos/control_protocol_state.cpp b/Development/nmos/control_protocol_state.cpp index 6f2777a6..fd6eb279 100644 --- a/Development/nmos/control_protocol_state.cpp +++ b/Development/nmos/control_protocol_state.cpp @@ -179,9 +179,9 @@ namespace nmos return get_datatype(resources, resource, arguments, is_deprecated, get_control_protocol_datatype_descriptor, gate); }; } - nmos::experimental::control_protocol_method_handler make_nc_get_properties_by_path_handler(get_properties_by_path_handler get_properties_by_path) + nmos::experimental::control_protocol_method_handler make_nc_get_properties_by_path_handler(get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_properties_by_path_handler get_properties_by_path) { - return [get_properties_by_path](nmos::resources& resources, const nmos::resource& resource, const web::json::value& arguments, bool is_deprecated, slog::base_gate& gate) + return [get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor, get_properties_by_path](nmos::resources& resources, const nmos::resource& resource, const web::json::value& arguments, bool is_deprecated, slog::base_gate& gate) { bool recurse = nmos::fields::nc::recurse(arguments); @@ -190,7 +190,7 @@ namespace nmos auto result = nmos::details::make_nc_method_result_error({ nmos::nc_method_status::method_not_implemented }, U("not implemented")); if (get_properties_by_path) { - result = get_properties_by_path(resource, recurse); + result = get_properties_by_path(get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor, resource, recurse); const auto& status = nmos::fields::nc::status(result); if (!web::http::is_error_status_code((web::http::status_code)status) && is_deprecated) @@ -201,11 +201,12 @@ namespace nmos return result; }; } - nmos::experimental::control_protocol_method_handler make_nc_validate_set_properties_by_path_handler(validate_set_properties_by_path_handler validate_set_properties_by_path) + nmos::experimental::control_protocol_method_handler make_nc_validate_set_properties_by_path_handler(get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, validate_set_properties_by_path_handler validate_set_properties_by_path) { - return [validate_set_properties_by_path](nmos::resources& resources, const nmos::resource& resource, const web::json::value& arguments, bool is_deprecated, slog::base_gate& gate) + return [get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor, validate_set_properties_by_path](nmos::resources& resources, const nmos::resource& resource, const web::json::value& arguments, bool is_deprecated, slog::base_gate& gate) { bool recurse = nmos::fields::nc::recurse(arguments); + const auto& included_property_traits = nmos::fields::nc::included_property_traits(arguments); const auto& data_set = nmos::fields::nc::data_set(arguments); if (data_set.is_null()) @@ -216,7 +217,7 @@ namespace nmos auto result = nmos::details::make_nc_method_result_error({ nmos::nc_method_status::method_not_implemented }, U("not implemented")); if (validate_set_properties_by_path) { - result = validate_set_properties_by_path(resource, data_set, recurse); + result = validate_set_properties_by_path(get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor, resource, data_set, recurse, included_property_traits); const auto& status = nmos::fields::nc::status(result); if (!web::http::is_error_status_code((web::http::status_code)status) && is_deprecated) @@ -227,12 +228,12 @@ namespace nmos return result; }; } - nmos::experimental::control_protocol_method_handler make_nc_set_properties_by_path_handler(set_properties_by_path_handler set_properties_by_path) + nmos::experimental::control_protocol_method_handler make_nc_set_properties_by_path_handler(get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, set_properties_by_path_handler set_properties_by_path) { - return [set_properties_by_path](nmos::resources& resources, const nmos::resource& resource, const web::json::value& arguments, bool is_deprecated, slog::base_gate& gate) + return [get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor, set_properties_by_path](nmos::resources& resources, const nmos::resource& resource, const web::json::value& arguments, bool is_deprecated, slog::base_gate& gate) { bool recurse = nmos::fields::nc::recurse(arguments); - bool allow_incomplete = nmos::fields::nc::allow_incomplete(arguments); + const auto& included_property_traits = nmos::fields::nc::included_property_traits(arguments); const auto& data_set = nmos::fields::nc::data_set(arguments); if (data_set.is_null()) @@ -243,7 +244,7 @@ namespace nmos auto result = nmos::details::make_nc_method_result_error({ nmos::nc_method_status::method_not_implemented }, U("not implemented")); if (set_properties_by_path) { - result = set_properties_by_path(resource, data_set, recurse, allow_incomplete); + result = set_properties_by_path(get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor, resource, data_set, recurse, included_property_traits); const auto& status = nmos::fields::nc::status(result); if (!web::http::is_error_status_code((web::http::status_code)status) && is_deprecated) @@ -394,9 +395,9 @@ namespace nmos to_vector(make_nc_bulk_properties_manager_properties()), to_methods_vector(make_nc_bulk_properties_manager_methods(), { - { nc_bulk_properties_manager_get_properties_by_path_method_id, details::make_nc_get_properties_by_path_handler(get_properties_by_path) }, - { nc_bulk_properties_manager_validate_set_properties_by_path_method_id, details::make_nc_validate_set_properties_by_path_handler(validate_set_properties_by_path) }, - { nc_bulk_properties_manager_set_properties_by_path_method_id, details::make_nc_set_properties_by_path_handler(set_properties_by_path) } + { nc_bulk_properties_manager_get_properties_by_path_method_id, details::make_nc_get_properties_by_path_handler(make_get_control_protocol_class_descriptor_handler(*this), make_get_control_protocol_datatype_descriptor_handler(*this), get_properties_by_path)}, + { nc_bulk_properties_manager_validate_set_properties_by_path_method_id, details::make_nc_validate_set_properties_by_path_handler(make_get_control_protocol_class_descriptor_handler(*this), make_get_control_protocol_datatype_descriptor_handler(*this), validate_set_properties_by_path) }, + { nc_bulk_properties_manager_set_properties_by_path_method_id, details::make_nc_set_properties_by_path_handler(make_get_control_protocol_class_descriptor_handler(*this), make_get_control_protocol_datatype_descriptor_handler(*this), set_properties_by_path) } }), to_vector(make_nc_bulk_properties_manager_events())) } }; @@ -480,9 +481,11 @@ namespace nmos { U("NcPayloadStatus"), {make_nc_payload_status_datatype()} }, // Device configuration feature set // TODO: add link + { U("NcPropertyTrait"), {make_nc_property_trait_datatype()} }, { U("NcPropertyValueHolder"), {make_nc_property_value_holder_datatype()}}, { U("NcObjectPropertiesHolder"), {make_nc_object_properties_holder_datatype()}}, { U("NcBulkValuesHolder"), {make_nc_bulk_values_holder_datatype()}}, + { U("NcRestoreValidationStatus"), {make_nc_restore_validation_status_datatype()}}, { U("NcObjectPropertiesSetValidation"), {make_nc_object_properties_set_validation_datatype()}}, { U("NcMethodResultBulkValuesHolder"), {make_nc_method_result_bulk_values_holder_datatype()}}, { U("NcMethodResultObjectPropertiesSetValidation"), {make_nc_method_result_object_properties_set_validation_datatype()}} diff --git a/Development/nmos/control_protocol_typedefs.h b/Development/nmos/control_protocol_typedefs.h index e65b03c4..9eaacc12 100644 --- a/Development/nmos/control_protocol_typedefs.h +++ b/Development/nmos/control_protocol_typedefs.h @@ -124,6 +124,33 @@ namespace nmos }; } + // Device Configuration + // NcPropertyTrait + namespace nc_property_trait + { + enum trait + { + instance_specific = 1, // Property is instance specific + ephemeral = 2, // Property is ephemeral + immutable = 3, // Property is immutable + device_generated = 4 // Property value is generated by the device + }; + } + + // NcRestoreValidationStatus + namespace nc_restore_validation_status + { + enum staus + { + ok = 200, // Restore was successful + excluded = 204, // Excluded from restore due to data provided in the request + invalid_data = 400, // Restore failed because relevant backup data set provided is invalid + not_found = 404, // Restore failed because the role path is not found in the device model or the device cannot create the role path from the data set + missing_dependency = 424, // Restore failed because of missing dependency information in the relevant backup data set + device_error = 500 // Restore failed due to an internal device error preventing the restore from happening + }; + } + // NcElementId // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Framework.html#ncelementid struct nc_element_id @@ -391,6 +418,9 @@ namespace nmos // See https://specs.amwa.tv/ms-05-02/branches/v1.0.x/docs/Blocks.html const nc_oid root_block_oid{ 1 }; const utility::string_t root_block_role{ U("root") }; + + // Device Configuration + const utility::string_t bulk_properties_manager_role{ root_block_role + U(".BulkPropertiesManager") }; } #endif diff --git a/Development/nmos/json_fields.h b/Development/nmos/json_fields.h index dc000c83..13d45afa 100644 --- a/Development/nmos/json_fields.h +++ b/Development/nmos/json_fields.h @@ -340,7 +340,8 @@ namespace nmos const web::json::field_as_string validation_fingerprint{ U("validationFingerprint") }; const web::json::field_as_string status_message{ U("statusMessage") }; const web::json::field_as_value data_set{ U("dataSet") }; // NcBulkValuesHolder - const web::json::field_as_bool allow_incomplete{ U("allowIncomplete") }; + const web::json::field_as_value traits{ U("traits") }; + const web::json::field_as_array included_property_traits{ U("includedPropertyTraits") }; } // NMOS Parameter Registers diff --git a/Development/nmos/node_server.cpp b/Development/nmos/node_server.cpp index c665a828..1e9a97da 100644 --- a/Development/nmos/node_server.cpp +++ b/Development/nmos/node_server.cpp @@ -77,7 +77,7 @@ namespace nmos // Configure the Configuration API - node_server.api_routers[{ {}, nmos::fields::configuration_port(node_model.settings) }].mount({}, nmos::make_configuration_api(node_model, validate_authorization ? validate_authorization(nmos::experimental::scopes::configuration) : nullptr, node_implementation.get_control_protocol_class_descriptor, node_implementation.get_control_protocol_datatype_descriptor, node_implementation.get_control_protocol_method_descriptor, node_implementation.get_properties_by_path, node_implementation.validate_set_properties_by_path, node_implementation.set_properties_by_path, node_implementation.control_protocol_property_changed, gate)); + node_server.api_routers[{ {}, nmos::fields::configuration_port(node_model.settings) }].mount({}, nmos::make_configuration_api(node_model, validate_authorization ? validate_authorization(nmos::experimental::scopes::configuration) : nullptr, node_implementation.get_control_protocol_class_descriptor, node_implementation.get_control_protocol_datatype_descriptor, node_implementation.get_control_protocol_method_descriptor, node_implementation.control_protocol_property_changed, gate)); const auto& events_ws_port = nmos::fields::events_ws_port(node_model.settings); auto& events_ws_api = node_server.ws_handlers[{ {}, nmos::fields::events_ws_port(node_model.settings) }];