Skip to content

Wrap RegexMatchAndSubstitute in a class#43802

Open
ravenblackx wants to merge 3 commits intoenvoyproxy:mainfrom
ravenblackx:regex_replace
Open

Wrap RegexMatchAndSubstitute in a class#43802
ravenblackx wants to merge 3 commits intoenvoyproxy:mainfrom
ravenblackx:regex_replace

Conversation

@ravenblackx
Copy link
Contributor

@ravenblackx ravenblackx commented Mar 5, 2026

Commit Message: Wrap RegexMatchAndSubstitute in a class
Additional Description: The proto RegexMatchAndSubstitute has repeated "copy this into two member variables and then manage the two variables" implementation. So that I don't have to add another identical implementation, this PR wraps the conversion from that proto message to a single object for performing regex substitution, to replace those two variables.

I put it in the Matcher namespace simply because the proto message is in a Matcher namespace.

Note: a thing that I noticed while creating this is that the implementation of regex replaceAll performs a frequently-unnecessary string copy - feels like there should be a replaceAllInPlace option that takes a stringref and just calls GlobalReplace. But because of the abstraction layers adding that might be a bit of a pain.

Risk Level: Small, change should be a no-op behavior-wise.
Testing: Existing tests of use-locations still pass, added explicit tests for the new class.
Docs Changes: n/a
Release Notes: n/a
Platform Specific Features: n/a

Signed-off-by: Raven Black <ravenblack@dropbox.com>
@repokitteh-read-only
Copy link

As a reminder, PRs marked as draft will not be automatically assigned reviewers,
or be handled by maintainer-oncall triage.

Please mark your PR as ready when you want it to be reviewed!

🐱

Caused by: #43802 was opened by ravenblackx.

see: more, trace.

@ravenblackx
Copy link
Contributor Author

/coverage

@repokitteh-read-only
Copy link

Coverage for this Pull Request will be rendered here:

https://storage.googleapis.com/envoy-cncf-pr/43802/coverage/index.html

For comparison, current coverage on main branch is here:

https://storage.googleapis.com/envoy-cncf-postsubmit/main/coverage/index.html

The coverage results are (re-)rendered each time the CI Envoy/Checks (coverage) job completes.

🐱

Caused by: a #43802 (comment) was created by @ravenblackx.

see: more, trace.

Signed-off-by: Raven Black <ravenblack@dropbox.com>
@ravenblackx ravenblackx marked this pull request as ready for review March 5, 2026 22:05
@ravenblackx ravenblackx requested a review from zuercher as a code owner March 5, 2026 22:05
@ravenblackx
Copy link
Contributor Author

@kyessenov I picked you to review because you've been doing string_view cleanups which feels like a similar sort of thing. :)

@ravenblackx
Copy link
Contributor Author

/retest


// If the proto has no pattern, returns a null RegexReplace.
static absl::StatusOr<RegexReplace>
create(Regex::Engine& engine, const ::envoy::type::matcher::v3::RegexMatchAndSubstitute& proto);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be cleaner as absl::StatusOr<absl::optional<RegexReplace>>? Then you don't have to handle special IsNull case and just store optional in-place.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly cleaner, but an optional makes it sizeof(RegexReplace) + sizeof(bool) is why I went this way. But fair enough, maybe linter-requiring clients to check is valuable enough to make it worth that little cost.

This also looked like it would make sense to transform optional into OptRef for the getter function to make sense, which surprisingly didn't already exist as a thing. Ideally I would implement this as an implicit constructor of OptRef since implicit conversion for this makes sense, but (after hours of trying to figure out what the problem was) there's an issue that you can do (and in places we do)

class Foo;  // forward-declared
OptRef<Foo> x;

but you can't do

class Foo;  // forward-declared
void someFn(absl::optional<Foo>& x) {...}

because operating on optional requires knowing the full non-abstract type. So you can't include an abstract constructor or even a static fromOptional member in OptRef, it has to be a separate helper function, yuck. (Also, two of them, to work with const.) And then even that's a problem because there's four combinations, const optional<const T>.

So I ended up just not doing OptRef, and having those getters return a reference to the existing optional, which is at least simple. What a timesink this was.

class RegexReplace {
public:
RegexReplace() = default;
RegexReplace(Regex::CompiledMatcherPtr regex, std::string&& substitution)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string_view instead of string&&. It works the same and more general.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string_view works the same as const std::string& - std::string&& is different in that it allows for creating an instance without performing a copy, when the caller doesn't need to keep a copy (and requires the caller to explicitly make the copy if they do need to keep a copy.)

So this is a bit more flexible for performance, at the cost of being slightly more verbose at the callsite (either explicit move or explicit copy) - which for most cases is just going to be the one instance in create anyway; all existing clients don't even use the constructor directly, but it seems reasonable to leave it open to direct construction.

regex_rewrite_substitution_ = rewrite_spec.substitution();
auto regex_replace_or = Matcher::RegexReplace::create(regex_engine, rewrite_spec);
SET_AND_RETURN_IF_NOT_OK(regex_replace_or.status(), creation_status);
regex_replace_ = std::move(regex_replace_or.value());
Copy link
Contributor

@kyessenov kyessenov Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: style guide recommends move(regex_replace_or).value() because then outer object is made invalid. That prevents subtle errors when touching it later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@kyessenov
Copy link
Contributor

/wait

Signed-off-by: Raven Black <ravenblack@dropbox.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants