Make ValFromJSON return zeek::expected instead of a variant

This commit is contained in:
Tim Wojtulewicz 2025-04-02 14:44:47 -07:00
parent e545fe8256
commit 201d4508e6
6 changed files with 64 additions and 60 deletions

View file

@ -891,8 +891,8 @@ unsigned int StringVal::ComputeFootprint(std::unordered_set<const Val*>* analyze
return 1 /* this object */ + static_cast<unsigned int>(Len()) / sizeof(Val); return 1 /* this object */ + static_cast<unsigned int>(Len()) / sizeof(Val);
} }
static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, const TypePtr& t, static zeek::expected<ValPtr, std::string> BuildVal(const rapidjson::Value& j, const TypePtr& t,
const FuncPtr& key_func) { const FuncPtr& key_func) {
auto mismatch_err = [t, &j]() { auto mismatch_err = [t, &j]() {
std::string json_type; std::string json_type;
switch ( j.GetType() ) { switch ( j.GetType() ) {
@ -906,7 +906,8 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
default: json_type = "unknown"; default: json_type = "unknown";
} }
return util::fmt("cannot convert JSON type '%s' to Zeek type '%s'", json_type.c_str(), type_name(t->Tag())); return zeek::unexpected<std::string>(
util::fmt("cannot convert JSON type '%s' to Zeek type '%s'", json_type.c_str(), type_name(t->Tag())));
}; };
if ( j.IsNull() ) if ( j.IsNull() )
@ -960,7 +961,7 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
parts.erase(std::remove_if(parts.begin(), parts.end(), [](auto x) { return x.empty(); }), parts.end()); parts.erase(std::remove_if(parts.begin(), parts.end(), [](auto x) { return x.empty(); }), parts.end());
if ( (parts.size() % 2) != 0 ) if ( (parts.size() % 2) != 0 )
return "wrong interval format, must be pairs of values with units"; return zeek::unexpected<std::string>("wrong interval format, must be pairs of values with units");
double interval_secs = 0.0; double interval_secs = 0.0;
for ( size_t i = 0; i < parts.size(); i += 2 ) { for ( size_t i = 0; i < parts.size(); i += 2 ) {
@ -980,7 +981,8 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
else if ( unit == "usec" || unit == "usecs" ) else if ( unit == "usec" || unit == "usecs" )
interval_secs += (value * Microseconds); interval_secs += (value * Microseconds);
else else
return util::fmt("wrong interval format, invalid unit type %s", unit.data()); return zeek::unexpected<std::string>(
util::fmt("wrong interval format, invalid unit type %s", unit.data()));
} }
return make_intrusive<IntervalVal>(interval_secs, Seconds); return make_intrusive<IntervalVal>(interval_secs, Seconds);
@ -991,11 +993,10 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
case TYPE_PORT: { case TYPE_PORT: {
if ( j.IsString() ) { if ( j.IsString() ) {
int port = 0;
if ( j.GetStringLength() > 0 && j.GetStringLength() < 10 ) { if ( j.GetStringLength() > 0 && j.GetStringLength() < 10 ) {
char* slash; char* slash;
errno = 0; errno = 0;
port = strtol(j.GetString(), &slash, 10); auto port = strtol(j.GetString(), &slash, 10);
if ( ! errno ) { if ( ! errno ) {
++slash; ++slash;
if ( util::streq(slash, "tcp") ) if ( util::streq(slash, "tcp") )
@ -1009,15 +1010,17 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
} }
} }
return "wrong port format, string must be /[0-9]{1,5}\\/(tcp|udp|icmp|unknown)/"; return zeek::unexpected<std::string>(
"wrong port format, string must be /[0-9]{1,5}\\/(tcp|udp|icmp|unknown)/");
} }
else if ( j.IsObject() ) { else if ( j.IsObject() ) {
if ( ! j.HasMember("port") || ! j.HasMember("proto") ) if ( ! j.HasMember("port") || ! j.HasMember("proto") )
return "wrong port format, object must have 'port' and 'proto' members"; return zeek::unexpected<std::string>(
"wrong port format, object must have 'port' and 'proto' members");
if ( ! j["port"].IsNumber() ) if ( ! j["port"].IsNumber() )
return "wrong port format, port must be a number"; return zeek::unexpected<std::string>("wrong port format, port must be a number");
if ( ! j["proto"].IsString() ) if ( ! j["proto"].IsString() )
return "wrong port format, protocol must be a string"; return zeek::unexpected<std::string>("wrong port format, protocol must be a string");
std::string proto{j["proto"].GetString()}; std::string proto{j["proto"].GetString()};
@ -1030,10 +1033,10 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
if ( proto == "unknown" ) if ( proto == "unknown" )
return val_mgr->Port(j["port"].GetInt(), TRANSPORT_UNKNOWN); return val_mgr->Port(j["port"].GetInt(), TRANSPORT_UNKNOWN);
return "wrong port format, invalid protocol string"; return zeek::unexpected<std::string>("wrong port format, invalid protocol string");
} }
else else
return "wrong port format, must be string or object"; return zeek::unexpected<std::string>("wrong port format, must be string or object");
} }
case TYPE_PATTERN: { case TYPE_PATTERN: {
@ -1055,7 +1058,7 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
auto re = std::make_unique<RE_Matcher>(candidate.c_str()); auto re = std::make_unique<RE_Matcher>(candidate.c_str());
if ( ! re->Compile() ) if ( ! re->Compile() )
return "error compiling pattern"; return zeek::unexpected<std::string>("error compiling pattern");
return make_intrusive<PatternVal>(re.release()); return make_intrusive<PatternVal>(re.release());
} }
@ -1074,7 +1077,7 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
std::string_view subnet_sv(j.GetString(), j.GetStringLength()); std::string_view subnet_sv(j.GetString(), j.GetStringLength());
auto pos = subnet_sv.find('/'); auto pos = subnet_sv.find('/');
if ( pos == subnet_sv.npos ) if ( pos == subnet_sv.npos )
return util::fmt("invalid value for subnet: '%s'", j.GetString()); return zeek::unexpected<std::string>(util::fmt("invalid value for subnet: '%s'", j.GetString()));
candidate = std::string(j.GetString(), pos); candidate = std::string(j.GetString(), pos);
@ -1082,7 +1085,7 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
char* end; char* end;
width = strtol(subnet_sv.data() + pos + 1, &end, 10); width = strtol(subnet_sv.data() + pos + 1, &end, 10);
if ( subnet_sv.data() + pos + 1 == end || errno ) if ( subnet_sv.data() + pos + 1 == end || errno )
return util::fmt("invalid value for subnet: '%s'", j.GetString()); return zeek::unexpected<std::string>(util::fmt("invalid value for subnet: '%s'", j.GetString()));
} }
if ( candidate.front() == '[' ) if ( candidate.front() == '[' )
@ -1104,7 +1107,8 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
auto intval = et->Lookup({j.GetString(), j.GetStringLength()}); auto intval = et->Lookup({j.GetString(), j.GetStringLength()});
if ( intval < 0 ) if ( intval < 0 )
return util::fmt("'%s' is not a valid enum for '%s'.", j.GetString(), et->GetName().c_str()); return zeek::unexpected<std::string>(
util::fmt("'%s' is not a valid enum for '%s'.", j.GetString(), et->GetName().c_str()));
return et->GetEnumVal(intval); return et->GetEnumVal(intval);
} }
@ -1126,19 +1130,19 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
return mismatch_err(); return mismatch_err();
for ( const auto& item : j.GetArray() ) { for ( const auto& item : j.GetArray() ) {
std::variant<ValPtr, std::string> v; zeek::expected<ValPtr, std::string> v;
if ( tl->GetTypes().size() == 1 ) if ( tl->GetTypes().size() == 1 )
v = BuildVal(item, tl->GetPureType(), key_func); v = BuildVal(item, tl->GetPureType(), key_func);
else else
v = BuildVal(item, tl, key_func); v = BuildVal(item, tl, key_func);
if ( ! get_if<ValPtr>(&v) ) if ( ! v )
return v; return v;
if ( ! std::get<ValPtr>(v) ) if ( v.value() == nullptr )
continue; continue;
tv->Assign(std::move(std::get<ValPtr>(v)), nullptr); tv->Assign(v.value(), nullptr);
} }
return tv; return tv;
@ -1151,7 +1155,7 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
rapidjson::Document idxstr; rapidjson::Document idxstr;
idxstr.Parse(it->name.GetString(), it->name.GetStringLength()); idxstr.Parse(it->name.GetString(), it->name.GetStringLength());
std::variant<ValPtr, std::string> idx; zeek::expected<ValPtr, std::string> idx;
if ( tl->GetTypes().size() > 1 ) if ( tl->GetTypes().size() > 1 )
idx = BuildVal(idxstr, tl, key_func); idx = BuildVal(idxstr, tl, key_func);
@ -1163,19 +1167,19 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
// Parse the string's content, not the full JSON string. // Parse the string's content, not the full JSON string.
idx = BuildVal(idxstr, tl->GetPureType(), key_func); idx = BuildVal(idxstr, tl->GetPureType(), key_func);
if ( ! get_if<ValPtr>(&idx) ) if ( ! idx )
return idx; return idx;
if ( ! std::get<ValPtr>(idx) ) if ( idx.value() == nullptr )
continue; continue;
auto v = BuildVal(it->value, tt->Yield(), key_func); auto v = BuildVal(it->value, tt->Yield(), key_func);
if ( ! get_if<ValPtr>(&v) ) if ( ! v )
return v; return v;
if ( ! std::get<ValPtr>(v) ) if ( v.value() == nullptr )
continue; continue;
tv->Assign(std::move(std::get<ValPtr>(idx)), std::move(std::get<ValPtr>(v))); tv->Assign(idx.value(), v.value());
} }
return tv; return tv;
@ -1202,7 +1206,7 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
} }
if ( ! result ) if ( ! result )
return "key function error"; return zeek::unexpected<std::string>("key function error");
normalized_keys[result->AsStringVal()->CheckString()] = &it->value; normalized_keys[result->AsStringVal()->CheckString()] = &it->value;
} }
@ -1226,17 +1230,18 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
if ( ! td_i->GetAttr(detail::ATTR_OPTIONAL) && ! td_i->GetAttr(detail::ATTR_DEFAULT) ) if ( ! td_i->GetAttr(detail::ATTR_OPTIONAL) && ! td_i->GetAttr(detail::ATTR_DEFAULT) )
// jval being set means it is a null JSON value else // jval being set means it is a null JSON value else
// it wasn't even there. // it wasn't even there.
return util::fmt("required field %s$%s is %s in JSON", t->GetName().c_str(), td_i->id, return zeek::unexpected<std::string>(util::fmt("required field %s$%s is %s in JSON",
jval ? "null" : "missing"); t->GetName().c_str(), td_i->id,
jval ? "null" : "missing"));
continue; continue;
} }
auto v = BuildVal(*jval, td_i->type, key_func); auto v = BuildVal(*jval, td_i->type, key_func);
if ( ! get_if<ValPtr>(&v) ) if ( ! v )
return v; return v;
rv->Assign(i, std::move(std::get<ValPtr>(v))); rv->Assign(i, v.value());
} }
return rv; return rv;
@ -1249,16 +1254,16 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
auto lt = t->AsTypeList(); auto lt = t->AsTypeList();
if ( j.GetArray().Size() < lt->GetTypes().size() ) if ( j.GetArray().Size() < lt->GetTypes().size() )
return "index type doesn't match"; return zeek::unexpected<std::string>("index type doesn't match");
auto lv = make_intrusive<ListVal>(TYPE_ANY); auto lv = make_intrusive<ListVal>(TYPE_ANY);
for ( size_t i = 0; i < lt->GetTypes().size(); i++ ) { for ( size_t i = 0; i < lt->GetTypes().size(); i++ ) {
auto v = BuildVal(j.GetArray()[i], lt->GetTypes()[i], key_func); auto v = BuildVal(j.GetArray()[i], lt->GetTypes()[i], key_func);
if ( ! get_if<ValPtr>(&v) ) if ( ! v )
return v; return v;
lv->Append(std::move(std::get<ValPtr>(v))); lv->Append(v.value());
} }
return lv; return lv;
@ -1272,29 +1277,30 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
auto vv = make_intrusive<VectorVal>(IntrusivePtr{NewRef{}, vt}); auto vv = make_intrusive<VectorVal>(IntrusivePtr{NewRef{}, vt});
for ( const auto& item : j.GetArray() ) { for ( const auto& item : j.GetArray() ) {
auto v = BuildVal(item, vt->Yield(), key_func); auto v = BuildVal(item, vt->Yield(), key_func);
if ( ! get_if<ValPtr>(&v) ) if ( ! v )
return v; return v;
if ( ! std::get<ValPtr>(v) ) if ( v.value() == nullptr )
continue; continue;
vv->Assign(vv->Size(), std::move(std::get<ValPtr>(v))); vv->Assign(vv->Size(), v.value());
} }
return vv; return vv;
} }
default: return util::fmt("type '%s' unsupported", type_name(t->Tag())); default: return zeek::unexpected<std::string>(util::fmt("type '%s' unsupported", type_name(t->Tag())));
} }
} }
std::variant<ValPtr, std::string> detail::ValFromJSON(std::string_view json_str, const TypePtr& t, zeek::expected<ValPtr, std::string> detail::ValFromJSON(std::string_view json_str, const TypePtr& t,
const FuncPtr& key_func) { const FuncPtr& key_func) {
rapidjson::Document doc; rapidjson::Document doc;
rapidjson::ParseResult ok = doc.Parse(json_str.data(), json_str.length()); rapidjson::ParseResult ok = doc.Parse(json_str.data(), json_str.length());
if ( ! ok ) if ( ! ok )
return util::fmt("JSON parse error: %s Offset: %lu", rapidjson::GetParseError_En(ok.Code()), ok.Offset()); return zeek::unexpected<std::string>(
util::fmt("JSON parse error: %s Offset: %lu", rapidjson::GetParseError_En(ok.Code()), ok.Offset()));
return BuildVal(doc, t, key_func); return BuildVal(doc, t, key_func);
} }

View file

@ -1774,8 +1774,8 @@ namespace detail {
// //
// The *key_func* parameter is a Zeek script function called for every JSON key // The *key_func* parameter is a Zeek script function called for every JSON key
// for normalization. If Func::nil is passed, no normalization happens. // for normalization. If Func::nil is passed, no normalization happens.
extern std::variant<ValPtr, std::string> ValFromJSON(std::string_view json_str, const TypePtr& t, extern zeek::expected<ValPtr, std::string> ValFromJSON(std::string_view json_str, const TypePtr& t,
const FuncPtr& key_func); const FuncPtr& key_func);
// If the given vector is an empty vector-of-any ("unspecified"), // If the given vector is an empty vector-of-any ("unspecified"),
// concretizes it to the given type. *v* gives the vector and *t* the // concretizes it to the given type. *v* gives the vector and *t* the

View file

@ -440,10 +440,10 @@ void Redis::HandleGetResult(redisReply* reply, ResultCallback* callback) {
res = ParseReplyError("get", reply->str); res = ParseReplyError("get", reply->str);
else { else {
auto val = zeek::detail::ValFromJSON(reply->str, val_type, Func::nil); auto val = zeek::detail::ValFromJSON(reply->str, val_type, Func::nil);
if ( std::holds_alternative<ValPtr>(val) ) if ( val )
res = {ReturnCode::SUCCESS, "", std::get<ValPtr>(val)}; res = {ReturnCode::SUCCESS, "", val.value()};
else else
res = {ReturnCode::OPERATION_FAILED, std::get<std::string>(val)}; res = {ReturnCode::OPERATION_FAILED, val.error()};
} }
freeReplyObject(reply); freeReplyObject(reply);

View file

@ -270,13 +270,11 @@ OperationResult SQLite::Step(sqlite3_stmt* stmt, bool parse_value) {
const char* text = (const char*)sqlite3_column_text(stmt, 0); const char* text = (const char*)sqlite3_column_text(stmt, 0);
auto val = zeek::detail::ValFromJSON(text, val_type, Func::nil); auto val = zeek::detail::ValFromJSON(text, val_type, Func::nil);
sqlite3_reset(stmt); sqlite3_reset(stmt);
if ( std::holds_alternative<ValPtr>(val) ) {
ValPtr val_v = std::get<ValPtr>(val); if ( val )
ret = {ReturnCode::SUCCESS, "", val_v}; ret = {ReturnCode::SUCCESS, "", val.value()};
} else
else { ret = {ReturnCode::OPERATION_FAILED, val.error()};
ret = {ReturnCode::OPERATION_FAILED, std::get<std::string>(val)};
}
} }
else { else {
ret = {ReturnCode::OPERATION_FAILED, "sqlite3_step should not have returned a value"}; ret = {ReturnCode::OPERATION_FAILED, "sqlite3_step should not have returned a value"};

View file

@ -1363,8 +1363,8 @@ RecordValPtr Supervisor::NodeConfig::ToRecord() const {
auto tt = rt->GetFieldType<TableType>("cluster"); auto tt = rt->GetFieldType<TableType>("cluster");
auto json_res = detail::ValFromJSON(cluster, tt, Func::nil); auto json_res = detail::ValFromJSON(cluster, tt, Func::nil);
if ( auto val = std::get_if<ValPtr>(&json_res) ) { if ( json_res ) {
rval->AssignField("cluster", *val); rval->AssignField("cluster", json_res.value());
} }
else { else {
// This should never happen: the JSON data comes from a table[string] of // This should never happen: the JSON data comes from a table[string] of
@ -1372,7 +1372,7 @@ RecordValPtr Supervisor::NodeConfig::ToRecord() const {
// here can be hard to debug. Other JSON code (see FromJSON()) fails // here can be hard to debug. Other JSON code (see FromJSON()) fails
// silently when the JSON is misformatted. We just warn: // silently when the JSON is misformatted. We just warn:
fprintf(stderr, "Could not parse %s's cluster table from '%s': %s\n", name.c_str(), cluster.c_str(), fprintf(stderr, "Could not parse %s's cluster table from '%s': %s\n", name.c_str(), cluster.c_str(),
std::get<std::string>(json_res).c_str()); json_res.error().c_str());
rval->AssignField("cluster", make_intrusive<TableVal>(std::move(tt))); rval->AssignField("cluster", make_intrusive<TableVal>(std::move(tt)));
} }

View file

@ -5268,15 +5268,15 @@ function from_json%(s: string, t: any, key_func: string_mapper &default=from_jso
auto res = zeek::detail::ValFromJSON(s->ToStdStringView(), t->AsType()->AsTypeType()->GetType(), auto res = zeek::detail::ValFromJSON(s->ToStdStringView(), t->AsType()->AsTypeType()->GetType(),
key_func_ptr); key_func_ptr);
if ( auto val = std::get_if<zeek::ValPtr>(&res) ) if ( res )
{ {
rval->Assign(v_idx, *val); rval->Assign(v_idx, res.value());
rval->Assign(valid_idx, true); rval->Assign(valid_idx, true);
} }
else else
{ {
rval->Assign(valid_idx, false); rval->Assign(valid_idx, false);
zeek::emit_builtin_error(std::get<std::string>(res).c_str()); zeek::emit_builtin_error(res.error().c_str());
} }
return std::move(rval); return std::move(rval);