Skip to content

Commit 306ec2c

Browse files
committed
METK-141: Add matcher, for select/exclude filtering of mars requests
1 parent 736ce0d commit 306ec2c

3 files changed

Lines changed: 147 additions & 0 deletions

File tree

src/metkit/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ list( APPEND metkit_srcs
2323
mars/ClientTask.h
2424
mars/DHSProtocol.cc
2525
mars/DHSProtocol.h
26+
mars/Matcher.cc
27+
mars/Matcher.h
2628
mars/MarsExpandContext.cc
2729
mars/MarsExpandContext.h
2830
mars/MarsExpansion.cc

src/metkit/mars/Matcher.cc

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* (C) Copyright 1996- ECMWF.
3+
*
4+
* This software is licensed under the terms of the Apache Licence Version 2.0
5+
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
6+
* In applying this licence, ECMWF does not waive the privileges and immunities
7+
* granted to it by virtue of its status as an intergovernmental organisation nor
8+
* does it submit to any jurisdiction.
9+
*/
10+
11+
/// @author Chris Bradley
12+
13+
#include <string>
14+
#include <map>
15+
16+
#include "eckit/utils/Regex.h"
17+
#include "eckit/utils/Tokenizer.h"
18+
#include "eckit/exception/Exceptions.h"
19+
#include "metkit/mars/MarsRequest.h"
20+
21+
#include "metkit/mars/Matcher.h"
22+
23+
namespace metkit::mars {
24+
25+
namespace { // helpers
26+
27+
using RegexMap = std::map<std::string, eckit::Regex>;
28+
using ValuesMap = std::map<std::string, std::vector<std::string>>;
29+
30+
RegexMap parseKeyRegexList(const std::string& expr) {
31+
RegexMap out;
32+
if (expr.empty()) {
33+
return out;
34+
}
35+
36+
std::vector<std::string> key_vals;
37+
eckit::Tokenizer(',')(expr, key_vals);
38+
39+
eckit::Tokenizer equals('=');
40+
for (const std::string& item : key_vals) {
41+
std::vector<std::string> kv;
42+
equals(item, kv);
43+
if (kv.size() != 2) {
44+
throw eckit::UserError("Invalid condition " + item + " in expression: " + expr, Here());
45+
}
46+
47+
const std::string& key = kv[0];
48+
const std::string& val = kv[1];
49+
50+
if (out.find(key) != out.end()) {
51+
throw eckit::UserError("Duplicate key " + key + " in expression: " + expr, Here());
52+
}
53+
54+
out[key] = eckit::Regex(val);
55+
}
56+
return out;
57+
}
58+
59+
} // namespace helpers
60+
61+
Matcher::Matcher(const std::string& expr):
62+
regexMap_{parseKeyRegexList(expr)} {
63+
}
64+
65+
bool Matcher::match(const MarsRequest& request, bool matchOnMissing, ValuePolicy policy) const {
66+
ValuesMap vals;
67+
for (const auto& kv : regexMap_) {
68+
vals[kv.first] = request.values(kv.first, /*emptyOK*/ true);
69+
}
70+
return match(vals, matchOnMissing, policy);
71+
}
72+
73+
bool Matcher::match(const ValuesMap& request, bool matchOnMissing, ValuePolicy policy) const {
74+
return std::all_of(regexMap_.begin(), regexMap_.end(),
75+
[&](const auto& kv) {
76+
const auto& keyword = kv.first;
77+
const auto& re = kv.second;
78+
79+
auto it = request.find(keyword);
80+
81+
// If the key is missing or present with no values
82+
if (it == request.end() || it->second.empty())
83+
return matchOnMissing;
84+
85+
const auto& vals = it->second;
86+
auto pred = [&](const std::string& s){ return re.match(s); };
87+
88+
if (policy == ValuePolicy::Any)
89+
return std::any_of(vals.begin(), vals.end(), pred);
90+
else if (policy == ValuePolicy::All)
91+
return std::all_of(vals.begin(), vals.end(), pred);
92+
else
93+
throw eckit::SeriousBug("Not implemented ValuePolicy in Matcher::match");
94+
});
95+
}
96+
97+
} // namespace metkit::mars

src/metkit/mars/Matcher.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* (C) Copyright 1996- ECMWF.
3+
*
4+
* This software is licensed under the terms of the Apache Licence Version 2.0
5+
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
6+
* In applying this licence, ECMWF does not waive the privileges and immunities
7+
* granted to it by virtue of its status as an intergovernmental organisation nor
8+
* does it submit to any jurisdiction.
9+
*/
10+
11+
/// @author Chris Bradley
12+
13+
#include <string>
14+
#include <map>
15+
#include "eckit/utils/Regex.h"
16+
#include "metkit/mars/MarsRequest.h"
17+
namespace metkit::mars {
18+
19+
// for handling things like:
20+
// expr: expver=(0001|o[0-9a-z]{3}),dataset=^climate-dt$
21+
// the expr is a series of comma separated key=regex pairs
22+
// where the regex is used to match against the value(s) of the key in a mars request
23+
24+
25+
class Matcher {
26+
public:
27+
enum class ValuePolicy { Any, All };
28+
public:
29+
// mars key -> regex
30+
using RegexMap = std::map<std::string, eckit::Regex>;
31+
32+
using SelectMap = std::map<std::string, eckit::Regex>;
33+
using ValuesMap = std::map<std::string, std::vector<std::string>>; // keyword -> list of values
34+
35+
public:
36+
Matcher(const std::string& expr);
37+
38+
bool match(const ValuesMap& request, bool matchOnMissing, ValuePolicy policy) const;
39+
bool match(const MarsRequest& request, bool matchOnMissing, ValuePolicy policy) const;
40+
41+
const RegexMap& regexMap() const { return regexMap_; }
42+
43+
private:
44+
45+
RegexMap regexMap_; // public for now...
46+
};
47+
48+
} // namespace metkit::mars

0 commit comments

Comments
 (0)