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);
}
static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, const TypePtr& t,
const FuncPtr& key_func) {
static zeek::expected<ValPtr, std::string> BuildVal(const rapidjson::Value& j, const TypePtr& t,
const FuncPtr& key_func) {
auto mismatch_err = [t, &j]() {
std::string json_type;
switch ( j.GetType() ) {
@ -906,7 +906,8 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
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() )
@ -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());
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;
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" )
interval_secs += (value * Microseconds);
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);
@ -991,11 +993,10 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
case TYPE_PORT: {
if ( j.IsString() ) {
int port = 0;
if ( j.GetStringLength() > 0 && j.GetStringLength() < 10 ) {
char* slash;
errno = 0;
port = strtol(j.GetString(), &slash, 10);
auto port = strtol(j.GetString(), &slash, 10);
if ( ! errno ) {
++slash;
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() ) {
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() )
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() )
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()};
@ -1030,10 +1033,10 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
if ( proto == "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
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: {
@ -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());
if ( ! re->Compile() )
return "error compiling pattern";
return zeek::unexpected<std::string>("error compiling pattern");
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());
auto pos = subnet_sv.find('/');
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);
@ -1082,7 +1085,7 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
char* end;
width = strtol(subnet_sv.data() + pos + 1, &end, 10);
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() == '[' )
@ -1104,7 +1107,8 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
auto intval = et->Lookup({j.GetString(), j.GetStringLength()});
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);
}
@ -1126,19 +1130,19 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
return mismatch_err();
for ( const auto& item : j.GetArray() ) {
std::variant<ValPtr, std::string> v;
zeek::expected<ValPtr, std::string> v;
if ( tl->GetTypes().size() == 1 )
v = BuildVal(item, tl->GetPureType(), key_func);
else
v = BuildVal(item, tl, key_func);
if ( ! get_if<ValPtr>(&v) )
if ( ! v )
return v;
if ( ! std::get<ValPtr>(v) )
if ( v.value() == nullptr )
continue;
tv->Assign(std::move(std::get<ValPtr>(v)), nullptr);
tv->Assign(v.value(), nullptr);
}
return tv;
@ -1151,7 +1155,7 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
rapidjson::Document idxstr;
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 )
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.
idx = BuildVal(idxstr, tl->GetPureType(), key_func);
if ( ! get_if<ValPtr>(&idx) )
if ( ! idx )
return idx;
if ( ! std::get<ValPtr>(idx) )
if ( idx.value() == nullptr )
continue;
auto v = BuildVal(it->value, tt->Yield(), key_func);
if ( ! get_if<ValPtr>(&v) )
if ( ! v )
return v;
if ( ! std::get<ValPtr>(v) )
if ( v.value() == nullptr )
continue;
tv->Assign(std::move(std::get<ValPtr>(idx)), std::move(std::get<ValPtr>(v)));
tv->Assign(idx.value(), v.value());
}
return tv;
@ -1202,7 +1206,7 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
}
if ( ! result )
return "key function error";
return zeek::unexpected<std::string>("key function error");
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) )
// jval being set means it is a null JSON value else
// it wasn't even there.
return util::fmt("required field %s$%s is %s in JSON", t->GetName().c_str(), td_i->id,
jval ? "null" : "missing");
return zeek::unexpected<std::string>(util::fmt("required field %s$%s is %s in JSON",
t->GetName().c_str(), td_i->id,
jval ? "null" : "missing"));
continue;
}
auto v = BuildVal(*jval, td_i->type, key_func);
if ( ! get_if<ValPtr>(&v) )
if ( ! v )
return v;
rv->Assign(i, std::move(std::get<ValPtr>(v)));
rv->Assign(i, v.value());
}
return rv;
@ -1249,16 +1254,16 @@ static std::variant<ValPtr, std::string> BuildVal(const rapidjson::Value& j, con
auto lt = t->AsTypeList();
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);
for ( size_t i = 0; i < lt->GetTypes().size(); i++ ) {
auto v = BuildVal(j.GetArray()[i], lt->GetTypes()[i], key_func);
if ( ! get_if<ValPtr>(&v) )
if ( ! v )
return v;
lv->Append(std::move(std::get<ValPtr>(v)));
lv->Append(v.value());
}
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});
for ( const auto& item : j.GetArray() ) {
auto v = BuildVal(item, vt->Yield(), key_func);
if ( ! get_if<ValPtr>(&v) )
if ( ! v )
return v;
if ( ! std::get<ValPtr>(v) )
if ( v.value() == nullptr )
continue;
vv->Assign(vv->Size(), std::move(std::get<ValPtr>(v)));
vv->Assign(vv->Size(), v.value());
}
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,
const FuncPtr& key_func) {
zeek::expected<ValPtr, std::string> detail::ValFromJSON(std::string_view json_str, const TypePtr& t,
const FuncPtr& key_func) {
rapidjson::Document doc;
rapidjson::ParseResult ok = doc.Parse(json_str.data(), json_str.length());
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);
}

View file

@ -1774,8 +1774,8 @@ namespace detail {
//
// The *key_func* parameter is a Zeek script function called for every JSON key
// 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,
const FuncPtr& key_func);
extern zeek::expected<ValPtr, std::string> ValFromJSON(std::string_view json_str, const TypePtr& t,
const FuncPtr& key_func);
// If the given vector is an empty vector-of-any ("unspecified"),
// 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);
else {
auto val = zeek::detail::ValFromJSON(reply->str, val_type, Func::nil);
if ( std::holds_alternative<ValPtr>(val) )
res = {ReturnCode::SUCCESS, "", std::get<ValPtr>(val)};
if ( val )
res = {ReturnCode::SUCCESS, "", val.value()};
else
res = {ReturnCode::OPERATION_FAILED, std::get<std::string>(val)};
res = {ReturnCode::OPERATION_FAILED, val.error()};
}
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);
auto val = zeek::detail::ValFromJSON(text, val_type, Func::nil);
sqlite3_reset(stmt);
if ( std::holds_alternative<ValPtr>(val) ) {
ValPtr val_v = std::get<ValPtr>(val);
ret = {ReturnCode::SUCCESS, "", val_v};
}
else {
ret = {ReturnCode::OPERATION_FAILED, std::get<std::string>(val)};
}
if ( val )
ret = {ReturnCode::SUCCESS, "", val.value()};
else
ret = {ReturnCode::OPERATION_FAILED, val.error()};
}
else {
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 json_res = detail::ValFromJSON(cluster, tt, Func::nil);
if ( auto val = std::get_if<ValPtr>(&json_res) ) {
rval->AssignField("cluster", *val);
if ( json_res ) {
rval->AssignField("cluster", json_res.value());
}
else {
// 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
// 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(),
std::get<std::string>(json_res).c_str());
json_res.error().c_str());
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(),
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);
}
else
{
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);