Generate error messages source
This file has gotten desynced from the standard, and maintaining it is
a pain. refactor the parse_registries.py script to generate this file
and update all wrong instances to the correct types.
To the extent possible, the generated code tries to be replaced with
1:1 identical structures to make review simpler.
Tested: On last patch in series.
Change-Id: Ic203a93fd26e0487475ce82c62beb6a22612368a
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/scripts/parse_registries.py b/scripts/parse_registries.py
index 194f134..573f8a7 100755
--- a/scripts/parse_registries.py
+++ b/scripts/parse_registries.py
@@ -188,6 +188,54 @@
)
+def get_response_code(entry_id, entry):
+ codes = {
+ "InternalError": "internal_server_error",
+ "OperationTimeout": "internal_server_error",
+ "PropertyValueResourceConflict": "conflict",
+ "ResourceInUse": "service_unavailable",
+ "ServiceTemporarilyUnavailable": "service_unavailable",
+ "ResourceCannotBeDeleted": "method_not_allowed",
+ "PropertyValueModified": "ok",
+ "InsufficientPrivilege": "forbidden",
+ "AccountForSessionNoLongerExists": "forbidden",
+ "ServiceDisabled": "service_unavailable",
+ "ServiceInUnknownState": "service_unavailable",
+ "EventSubscriptionLimitExceeded": "service_unavailable",
+ "ResourceAtUriUnauthorized": "unauthorized",
+ "SessionTerminated": "ok",
+ "SubscriptionTerminated": "ok",
+ "PropertyNotWritable": "forbidden",
+ "MaximumErrorsExceeded": "internal_server_error",
+ "GeneralError": "internal_server_error",
+ "PreconditionFailed": "precondition_failed",
+ "OperationFailed": "bad_gateway",
+ "ServiceShuttingDown": "service_unavailable",
+ "AccountRemoved": "ok",
+ "PropertyValueExternalConflict": "conflict",
+ "InsufficientStorage": "insufficient_storage",
+ "OperationNotAllowed": "method_not_allowed",
+ "ResourceNotFound": "not_found",
+ "CouldNotEstablishConnection": "not_found",
+ "AccessDenied": "forbidden",
+ "Success": None,
+ "Created": "created",
+ "NoValidSession": "forbidden",
+ "SessionLimitExceeded": "service_unavailable",
+ "ResourceExhaustion": "service_unavailable",
+ "AccountModified": "ok",
+ "PasswordChangeRequired": None,
+ "ResourceInStandby": "service_unavailable",
+ "GenerateSecretKeyRequired": "forbidden",
+ }
+
+ code = codes.get(entry_id, "NOCODE")
+ if code != "NOCODE":
+ return code
+
+ return "bad_request"
+
+
def get_old_index(entry):
old_order = [
"ResourceInUse",
@@ -285,7 +333,7 @@
return 999999
-def create_error_registry(entry):
+def make_error_function(entry_id, entry, is_header):
arg_as_url = {
"AccessDenied": [1],
@@ -329,6 +377,128 @@
"InvalidIndex": [1],
}
+ out = ""
+ args = []
+ argtypes = []
+ for arg_index, arg in enumerate(entry.get("ParamTypes", [])):
+ arg_index += 1
+ if arg_index in arg_as_url.get(entry_id, []):
+ typename = "const boost::urls::url_view_base&"
+ elif arg_index in arg_as_json.get(entry_id, []):
+ typename = "const nlohmann::json&"
+ elif arg_index in arg_as_int.get(entry_id, []):
+ typename = "int"
+ elif arg_index in arg_as_uint64.get(entry_id, []):
+ typename = "uint64_t"
+ elif arg_index in arg_as_int64.get(entry_id, []):
+ typename = "int64_t"
+ else:
+ typename = "std::string_view"
+ argtypes.append(typename)
+ args.append(f"{typename} arg{arg_index}")
+ function_name = entry_id[0].lower() + entry_id[1:]
+ arg = ", ".join(args)
+ out += f"nlohmann::json {function_name}({arg})"
+
+ if is_header:
+ out += ";\n\n"
+ else:
+ out += "\n{\n"
+ to_array_type = ""
+ if argtypes:
+ outargs = []
+ for index, typename in enumerate(argtypes):
+ index += 1
+ if typename == "const nlohmann::json&":
+ out += f"std::string arg{index}Str = arg{index}.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace);\n"
+ elif typename in ("int64_t", "int", "uint64_t"):
+ out += f"std::string arg{index}Str = std::to_string(arg{index});\n"
+
+ for index, typename in enumerate(argtypes):
+ index += 1
+ if typename == "const boost::urls::url_view_base&":
+ outargs.append(f"arg{index}.buffer()")
+ to_array_type = "<std::string_view>"
+ elif typename == "const nlohmann::json&":
+ outargs.append(f"arg{index}Str")
+ to_array_type = "<std::string_view>"
+ elif typename in ("int64_t", "int", "uint64_t"):
+ outargs.append(f"arg{index}Str")
+ to_array_type = "<std::string_view>"
+ else:
+ outargs.append(f"arg{index}")
+ argstring = ", ".join(outargs)
+ # out += f" std::array<std::string_view, {len(argtypes)}> args{{{argstring}}};\n"
+
+ if argtypes:
+ arg_param = f"std::to_array{to_array_type}({{{argstring}}})"
+ else:
+ arg_param = "{}"
+ out += f" return getLog(redfish::registries::base::Index::{function_name}, {arg_param});"
+ out += "\n}\n\n"
+ args.insert(0, "crow::Response& res")
+ if entry_id == "InternalError":
+ if is_header:
+ args.append(
+ "std::source_location location = std::source_location::current()"
+ )
+ else:
+ args.append("const std::source_location location")
+ arg = ", ".join(args)
+ out += f"void {function_name}({arg})"
+ if is_header:
+ out += ";\n"
+ else:
+ out += "\n{\n"
+ if entry_id == "InternalError":
+ out += """BMCWEB_LOG_CRITICAL("Internal Error {}({}:{}) `{}`: ", location.file_name(),
+ location.line(), location.column(),
+ location.function_name());\n"""
+
+ if entry_id == "ServiceTemporarilyUnavailable":
+ out += (
+ "res.addHeader(boost::beast::http::field::retry_after, arg1);"
+ )
+
+ res = get_response_code(entry_id, entry)
+ if res:
+ out += f" res.result(boost::beast::http::status::{res});\n"
+ args_out = ", ".join([f"arg{x+1}" for x in range(len(argtypes))])
+
+ addMessageToJson = {
+ "PropertyDuplicate": 1,
+ "ResourceAlreadyExists": 2,
+ "CreateFailedMissingReqProperties": 1,
+ "PropertyValueFormatError": 2,
+ "PropertyValueNotInList": 2,
+ "PropertyValueTypeError": 2,
+ "PropertyValueError": 1,
+ "PropertyNotWritable": 1,
+ "PropertyValueModified": 1,
+ "PropertyMissing": 1,
+ }
+
+ addMessageToRoot = [
+ "SessionTerminated",
+ "SubscriptionTerminated",
+ "AccountRemoved",
+ "Created",
+ "Success",
+ "PasswordChangeRequired",
+ ]
+
+ if entry_id in addMessageToJson:
+ out += f" addMessageToJson(res.jsonValue, {function_name}({args_out}), arg{addMessageToJson[entry_id]});\n"
+ elif entry_id in addMessageToRoot:
+ out += f" addMessageToJsonRoot(res.jsonValue, {function_name}({args_out}));\n"
+ else:
+ out += f" addMessageToErrorJson(res.jsonValue, {function_name}({args_out}));\n"
+ out += "}\n"
+ out += "\n"
+ return out
+
+
+def create_error_registry(entry):
file, json_dict, namespace, url = entry
messages = OrderedDict(
@@ -396,35 +566,191 @@
out.write("*\n")
out.write(f"* @returns Message {entry_id} formatted to JSON */\n")
- args = []
- for arg_index, arg in enumerate(entry.get("ParamTypes", [])):
- arg_index += 1
- if arg_index in arg_as_url.get(entry_id, []):
- typename = "const boost::urls::url_view_base&"
- elif arg_index in arg_as_json.get(entry_id, []):
- typename = "const nlohmann::json&"
- elif arg_index in arg_as_int.get(entry_id, []):
- typename = "int"
- elif arg_index in arg_as_uint64.get(entry_id, []):
- typename = "uint64_t"
- elif arg_index in arg_as_int64.get(entry_id, []):
- typename = "int64_t"
- else:
- typename = "std::string_view"
- args.append(f"{typename} arg{arg_index}")
- function_name = entry_id[0].lower() + entry_id[1:]
- arg = ", ".join(args)
- out.write(f"nlohmann::json {function_name}({arg});\n\n")
- args.insert(0, "crow::Response& res")
- if entry_id == "InternalError":
- args.append(
- "std::source_location location = std::source_location::current()"
- )
- arg = ", ".join(args)
- out.write(f"void {function_name}({arg});\n\n")
+ out.write(make_error_function(entry_id, entry, True))
out.write(" }\n")
out.write("}\n")
- os.system(f"clang-format -i {error_messages_hpp}")
+
+ error_messages_cpp = os.path.join(
+ SCRIPT_DIR, "..", "redfish-core", "src", "error_messages.cpp"
+ )
+ with open(
+ error_messages_cpp,
+ "w",
+ ) as out:
+ out.write(WARNING)
+ out.write(
+ """
+#include "error_messages.hpp"
+
+#include "http_response.hpp"
+#include "logging.hpp"
+#include "registries.hpp"
+#include "registries/base_message_registry.hpp"
+
+#include <boost/beast/http/field.hpp>
+#include <boost/beast/http/status.hpp>
+#include <boost/url/url_view_base.hpp>
+#include <nlohmann/json.hpp>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <source_location>
+#include <span>
+#include <string>
+#include <string_view>
+
+// Clang can't seem to decide whether this header needs to be included or not,
+// and is inconsistent. Include it for now
+// NOLINTNEXTLINE(misc-include-cleaner)
+#include <utility>
+
+namespace redfish
+{
+
+namespace messages
+{
+
+static void addMessageToErrorJson(nlohmann::json& target,
+ const nlohmann::json& message)
+{
+ auto& error = target["error"];
+
+ // If this is the first error message, fill in the information from the
+ // first error message to the top level struct
+ if (!error.is_object())
+ {
+ auto messageIdIterator = message.find("MessageId");
+ if (messageIdIterator == message.end())
+ {
+ BMCWEB_LOG_CRITICAL(
+ "Attempt to add error message without MessageId");
+ return;
+ }
+
+ auto messageFieldIterator = message.find("Message");
+ if (messageFieldIterator == message.end())
+ {
+ BMCWEB_LOG_CRITICAL("Attempt to add error message without Message");
+ return;
+ }
+ error["code"] = *messageIdIterator;
+ error["message"] = *messageFieldIterator;
+ }
+ else
+ {
+ // More than 1 error occurred, so the message has to be generic
+ error["code"] = std::string(messageVersionPrefix) + "GeneralError";
+ error["message"] = "A general error has occurred. See Resolution for "
+ "information on how to resolve the error.";
+ }
+
+ // This check could technically be done in the default construction
+ // branch above, but because we need the pointer to the extended info field
+ // anyway, it's more efficient to do it here.
+ auto& extendedInfo = error[messages::messageAnnotation];
+ if (!extendedInfo.is_array())
+ {
+ extendedInfo = nlohmann::json::array();
+ }
+
+ extendedInfo.push_back(message);
+}
+
+void moveErrorsToErrorJson(nlohmann::json& target, nlohmann::json& source)
+{
+ if (!source.is_object())
+ {
+ return;
+ }
+ auto errorIt = source.find("error");
+ if (errorIt == source.end())
+ {
+ // caller puts error message in root
+ messages::addMessageToErrorJson(target, source);
+ source.clear();
+ return;
+ }
+ auto extendedInfoIt = errorIt->find(messages::messageAnnotation);
+ if (extendedInfoIt == errorIt->end())
+ {
+ return;
+ }
+ const nlohmann::json::array_t* extendedInfo =
+ (*extendedInfoIt).get_ptr<const nlohmann::json::array_t*>();
+ if (extendedInfo == nullptr)
+ {
+ source.erase(errorIt);
+ return;
+ }
+ for (const nlohmann::json& message : *extendedInfo)
+ {
+ addMessageToErrorJson(target, message);
+ }
+ source.erase(errorIt);
+}
+
+static void addMessageToJsonRoot(nlohmann::json& target,
+ const nlohmann::json& message)
+{
+ if (!target[messages::messageAnnotation].is_array())
+ {
+ // Force object to be an array
+ target[messages::messageAnnotation] = nlohmann::json::array();
+ }
+
+ target[messages::messageAnnotation].push_back(message);
+}
+
+static void addMessageToJson(nlohmann::json& target,
+ const nlohmann::json& message,
+ std::string_view fieldPath)
+{
+ std::string extendedInfo(fieldPath);
+ extendedInfo += messages::messageAnnotation;
+
+ nlohmann::json& field = target[extendedInfo];
+ if (!field.is_array())
+ {
+ // Force object to be an array
+ field = nlohmann::json::array();
+ }
+
+ // Object exists and it is an array so we can just push in the message
+ field.push_back(message);
+}
+
+static nlohmann::json getLog(redfish::registries::base::Index name,
+ std::span<const std::string_view> args)
+{
+ size_t index = static_cast<size_t>(name);
+ if (index >= redfish::registries::base::registry.size())
+ {
+ return {};
+ }
+ return getLogFromRegistry(redfish::registries::base::header,
+ redfish::registries::base::registry, index, args);
+}
+
+"""
+ )
+ for entry_id, entry in messages.items():
+ out.write(
+ f"""/**
+ * @internal
+ * @brief Formats {entry_id} message into JSON
+ *
+ * See header file for more information
+ * @endinternal
+ */
+"""
+ )
+ message = entry["Message"]
+ out.write(make_error_function(entry_id, entry, False))
+
+ out.write(" }\n")
+ out.write("}\n")
+ os.system(f"clang-format -i {error_messages_hpp} {error_messages_cpp}")
def make_privilege_registry():