Files
salmanoff/include/user/deviceAttachmentSpec.h
hayodea b198f6a42b Add shared DeviceAttachmentSpec param parsing helpers.
Centralize DAP param lookup, parsing, and validation primitives so stimBuff
and threshold modules can share one implementation instead of duplicating
parse logic per plugin.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 11:00:22 -04:00

492 lines
15 KiB
C++

#ifndef SENSORDEVICESPEC_H
#define SENSORDEVICESPEC_H
#include <array>
#include <cctype>
#include <optional>
#include <string_view>
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <stdexcept>
#include <utility>
namespace smo {
namespace device {
/* Carrier used by the DAP spec parser to pass one parenthesized segment
* (e.g. postrin(interest-pc=85) or pcloudLightAmbience(...)) up the reduction
* stack; the spec_body reduction classifies segments and populates the
* DeviceAttachmentSpec. Defined here so both the parser header and consumers
* of the generated header see the type.
*/
struct DapSegment
{
std::string name;
std::vector<std::pair<std::string, std::string>> params;
};
class DeviceAttachmentSpec
{
public:
friend std::ostream& operator<<(
std::ostream& os, const DeviceAttachmentSpec& spec)
{
os << spec.stringify();
return os;
}
bool operator==(const DeviceAttachmentSpec& other) const
{
return deviceIdentifier == other.deviceIdentifier &&
sensorType == other.sensorType &&
postrin == other.postrin &&
negtrin == other.negtrin &&
qualeIfaceApi == other.qualeIfaceApi &&
stimBuffApi == other.stimBuffApi &&
provider == other.provider &&
deviceSelector == other.deviceSelector;
}
public:
std::string deviceIdentifier;
char sensorType;
/* postrin/negtrin hold the literal segment name ("postrin" /
* "negtrin") when present, empty string when the DAP spec omits the
* corresponding intrin specifier. Params vectors carry the params from
* within the postrin(...)/negtrin(...) segment.
*/
std::string postrin;
std::vector<std::pair<std::string,std::string>> postrinParams;
std::string negtrin;
std::vector<std::pair<std::string,std::string>> negtrinParams;
std::string qualeIfaceApi;
std::vector<std::pair<std::string,std::string>> qualeIfaceApiParams;
std::string stimBuffApi;
std::vector<std::pair<std::string,std::string>> stimBuffApiParams;
std::string provider;
std::vector<std::pair<std::string,std::string>> providerParams;
std::string deviceSelector;
static void stringifyParams(
std::ostream& os,
const std::vector<std::pair<std::string,std::string>>& params)
{
for (const auto& param : params)
{
os << param.first;
if (!param.second.empty()) {
os << "=" << param.second;
}
os << " ";
}
}
std::string stringify() const
{
std::ostringstream os;
os << "Device Identifier: " << deviceIdentifier
<< ", Sensor Type: " << sensorType;
if (!postrin.empty())
{
os << ", Postrin Params: (";
stringifyParams(os, postrinParams);
os << ")";
}
if (!negtrin.empty())
{
os << ", Negtrin Params: (";
stringifyParams(os, negtrinParams);
os << ")";
}
os << ", QualeIface API: " << qualeIfaceApi << ", QualeIface API Params: (";
for (const auto& param : qualeIfaceApiParams)
{
os << param.first;
if (!param.second.empty()) {
os << "=" << param.second;
}
os << " ";
}
os << "), StimBuff API: " << stimBuffApi
<< ", StimBuff API Params: (";
for (const auto& param : stimBuffApiParams)
{
os << param.first;
if (!param.second.empty()) {
os << "=" << param.second;
}
os << " ";
}
os << "), Provider: " << provider << ", Provider Params: (";
for (const auto& param : providerParams)
{
os << param.first;
if (!param.second.empty()) {
os << "=" << param.second;
}
os << " ";
}
os << "), Device Selector: " << deviceSelector;
return os.str();
}
/**
* @brief Parse a required integer parameter from a parameter list
* @param params The parameter vector to search in
* @param paramName The name of the parameter to parse
* @return The parsed integer value
* @throws std::runtime_error if parameter is not found or cannot be parsed
* @note The lattermost supplied matching param wins if multiple are present
*/
static int parseRequiredParamAsInt(
const std::vector<std::pair<std::string,std::string>>& params,
const std::string& paramName
)
{
return parseRequiredParamAsIntWithSynonyms(
params,
std::array<std::string_view, 1>{{paramName}},
"No " + paramName + " specified in params");
}
/**
* @brief Parse an optional integer parameter from a parameter list
* @param params The parameter vector to search in
* @param paramName The name of the parameter to parse
* @param defaultValue The default value to return if no parameter is found
* @return The parsed integer value, or defaultValue if none found
* @note The lattermost supplied matching param wins if multiple are present
*/
static int parseOptionalParamAsInt(
const std::vector<std::pair<std::string,std::string>>& params,
const std::string& paramName,
int defaultValue
)
{
return parseOptionalParamAsIntWithSynonyms(
params, std::array<std::string_view, 1>{{paramName}}, defaultValue);
}
/**
* @brief Test whether a parameter name appears in a synonym collection
*/
template <typename SynonymCollectionT>
static bool namesContain(
const SynonymCollectionT& synonymNames,
std::string_view paramName)
{
return std::find(
std::begin(synonymNames),
std::end(synonymNames),
paramName) != std::end(synonymNames);
}
/**
* @brief Test whether a parameter list contains an exact parameter name
*/
static bool paramsContain(
const std::vector<std::pair<std::string,std::string>>& params,
std::string_view paramName)
{
for (const auto& param : params)
{
if (param.first == paramName) {
return true;
}
}
return false;
}
/**
* @brief Test whether a parameter name matches any synonym collection
*/
template <typename... SynonymCollectionTs>
static bool paramNameMatchesAnySynonymGroup(
std::string_view paramName,
const SynonymCollectionTs&... synonymGroups)
{
return (namesContain(synonymGroups, paramName) || ...);
}
/**
* @brief Trim surrounding whitespace and lowercase a DAP param token
*/
static std::string normalizeParamToken(std::string token)
{
while (!token.empty()
&& std::isspace(static_cast<unsigned char>(token.back())))
{
token.pop_back();
}
while (!token.empty()
&& std::isspace(static_cast<unsigned char>(token.front())))
{
token.erase(token.begin());
}
for (char& character : token)
{
character = static_cast<char>(
std::tolower(static_cast<unsigned char>(character)));
}
return token;
}
/**
* @brief Find the lattermost param matching any synonym (name + value pair)
*/
template <typename SynonymCollectionT>
static std::optional<std::pair<std::string, std::string>>
findOptionalParamWithSynonyms(
const std::vector<std::pair<std::string,std::string>>& params,
const SynonymCollectionT& synonymNames)
{
for (auto paramIt = params.rbegin(); paramIt != params.rend(); ++paramIt)
{
if (namesContain(synonymNames, paramIt->first)) {
return *paramIt;
}
}
return std::nullopt;
}
/**
* @brief Find the lattermost param with an exact name (name + value pair)
*/
static std::optional<std::pair<std::string, std::string>> findOptionalParam(
const std::vector<std::pair<std::string,std::string>>& params,
std::string_view paramName)
{
return findOptionalParamWithSynonyms(
params, std::array<std::string_view, 1>{{paramName}});
}
/**
* @brief Parse a DAP integer param value
* @throws std::runtime_error if parsing fails
*/
static int parseParamValueAsInt(
const std::string& paramName,
const std::string& paramValue)
{
try {
return std::stoi(paramValue);
}
catch (const std::invalid_argument&)
{
throw std::runtime_error(
"Failed to parse '" + paramName + "' param value '"
+ paramValue + "' as integer");
}
catch (const std::out_of_range&)
{
throw std::runtime_error(
"'" + paramName + "' param value '" + paramValue
+ "' is out of range");
}
}
/**
* @brief Parse a DAP integer param value; result must be strictly positive
* @throws std::runtime_error if parsing fails or value is not positive
*/
static int parseParamValueAsPositiveInt(
const std::string& paramName,
const std::string& paramValue)
{
const int parsedValue = parseParamValueAsInt(paramName, paramValue);
if (parsedValue <= 0)
{
throw std::runtime_error(
"'" + paramName + "' must be positive");
}
return parsedValue;
}
/**
* @brief Parse a DAP boolean param value
* @param value Raw param value; empty string means @p emptyMeansTrue
* @throws std::runtime_error if the value is not recognized
*/
static bool parseParamValueAsBool(
const std::string& value,
bool emptyMeansTrue = true)
{
if (value.empty()) {
return emptyMeansTrue;
}
const std::string lowered = normalizeParamToken(value);
if (lowered == "true" || lowered == "1" || lowered == "yes") {
return true;
}
if (lowered == "false" || lowered == "0" || lowered == "no") {
return false;
}
throw std::runtime_error(
"Boolean param value '" + value
+ "' is not recognized (use true/false, 1/0, yes/no, or omit value)");
}
/**
* @brief Parse a required integer param using synonyms
* @note The lattermost supplied matching param wins if multiple are present
*/
template <typename SynonymCollectionT>
static int parseRequiredParamAsIntWithSynonyms(
const std::vector<std::pair<std::string,std::string>>& params,
const SynonymCollectionT& synonymNames,
const std::string& missingParamMessage)
{
const std::optional<std::pair<std::string, std::string>> matchedParam =
findOptionalParamWithSynonyms(params, synonymNames);
if (!matchedParam)
{
throw std::runtime_error(missingParamMessage);
}
return parseParamValueAsInt(matchedParam->first, matchedParam->second);
}
/**
* @brief Parse an optional integer parameter from a parameter list using synonyms
* @param params The parameter vector to search in
* @param synonymNames The collection of synonymous parameter names to try
* @param defaultValue The default value to return if no parameter is found
* @return The parsed integer value, or defaultValue if none found
* @note The lattermost supplied matching param wins if multiple are present
*/
template <typename SynonymCollectionT>
static int parseOptionalParamAsIntWithSynonyms(
const std::vector<std::pair<std::string,std::string>>& params,
const SynonymCollectionT& synonymNames,
int defaultValue
)
{
const std::optional<std::pair<std::string, std::string>> matchedParam =
findOptionalParamWithSynonyms(params, synonymNames);
if (!matchedParam) {
return defaultValue;
}
return parseParamValueAsInt(matchedParam->first, matchedParam->second);
}
/**
* @brief Parse a required non-empty param value using synonyms
* @note The lattermost supplied matching param wins if multiple are present
* @throws std::runtime_error if no matching param is found or value is empty
*/
template <typename SynonymCollectionT>
static std::string parseRequiredParamValueWithSynonyms(
const std::vector<std::pair<std::string,std::string>>& params,
const SynonymCollectionT& synonymNames,
const std::string& missingOrEmptyValueMessage)
{
const std::optional<std::pair<std::string, std::string>> matchedParam =
findOptionalParamWithSynonyms(params, synonymNames);
if (!matchedParam || matchedParam->second.empty())
{
throw std::runtime_error(missingOrEmptyValueMessage);
}
return matchedParam->second;
}
/**
* @brief Parse an optional non-empty param value using synonyms
* @return Parsed value, or std::nullopt when the param is absent
* @throws std::runtime_error if the param is present but its value is empty
*/
template <typename SynonymCollectionT>
static std::optional<std::string> parseOptionalParamValueWithSynonyms(
const std::vector<std::pair<std::string,std::string>>& params,
const SynonymCollectionT& synonymNames,
const std::string& emptyValueMessage)
{
const std::optional<std::pair<std::string, std::string>> matchedParam =
findOptionalParamWithSynonyms(params, synonymNames);
if (!matchedParam) {
return std::nullopt;
}
if (matchedParam->second.empty()) {
throw std::runtime_error(emptyValueMessage);
}
return matchedParam->second;
}
/**
* @brief Parse an optional boolean param using synonyms
* @note The lattermost supplied matching param wins if multiple are present
*/
template <typename SynonymCollectionT>
static bool parseOptionalParamAsBoolWithSynonyms(
const std::vector<std::pair<std::string,std::string>>& params,
const SynonymCollectionT& synonymNames,
bool defaultValue = false,
bool emptyMeansTrue = true)
{
const std::optional<std::pair<std::string, std::string>> matchedParam =
findOptionalParamWithSynonyms(params, synonymNames);
if (!matchedParam) {
return defaultValue;
}
return parseParamValueAsBool(matchedParam->second, emptyMeansTrue);
}
/**
* @brief Reject params whose names are not covered by any synonym group
*/
template <typename... SynonymCollectionTs>
static void rejectUnknownParams(
const std::vector<std::pair<std::string,std::string>>& params,
const std::string& unknownParamMessagePrefix,
const SynonymCollectionTs&... synonymGroups)
{
for (const auto& param : params)
{
if (!paramNameMatchesAnySynonymGroup(
param.first, synonymGroups...))
{
throw std::runtime_error(
unknownParamMessagePrefix + param.first + "'");
}
}
}
};
class InteroceptorDevAttachmentSpec : public DeviceAttachmentSpec
{
};
class ExtrospectorDevAttachmentSpec : public DeviceAttachmentSpec
{
};
} // namespace device
} // namespace smo
#endif // SENSORDEVICESPEC_H