2017-02-07 16:53:16 -05:00
package rewrite
import (
2018-07-08 03:18:01 -04:00
"context"
2017-12-13 11:31:19 -05:00
"fmt"
"regexp"
"strconv"
"strings"
2018-02-14 07:00:04 -08:00
"github.com/coredns/coredns/plugin"
2018-07-02 15:39:50 +01:00
"github.com/coredns/coredns/request"
2021-05-04 10:05:45 +02:00
"github.com/miekg/dns"
2017-02-07 16:53:16 -05:00
)
2026-01-05 19:48:48 +02:00
// maxRegexpLen is a hard limit on the length of a regex pattern to prevent
// OOM during regex compilation with malicious input.
const maxRegexpLen = 10000
2021-05-04 10:05:45 +02:00
// stringRewriter rewrites a string
type stringRewriter interface {
rewriteString ( src string ) string
2017-12-13 11:31:19 -05:00
}
2021-05-04 10:05:45 +02:00
// regexStringRewriter can be used to rewrite strings by regex pattern.
// it contains all the information required to detect and execute a rewrite
// on a string.
type regexStringRewriter struct {
pattern * regexp . Regexp
replacement string
2017-02-07 16:53:16 -05:00
}
2021-05-04 10:05:45 +02:00
var _ stringRewriter = & regexStringRewriter { }
func newStringRewriter ( pattern * regexp . Regexp , replacement string ) stringRewriter {
return & regexStringRewriter { pattern , replacement }
2017-02-07 16:53:16 -05:00
}
2021-05-04 10:05:45 +02:00
func ( r * regexStringRewriter ) rewriteString ( src string ) string {
regexGroups := r . pattern . FindStringSubmatch ( src )
if len ( regexGroups ) == 0 {
return src
}
s := r . replacement
for groupIndex , groupValue := range regexGroups {
groupIndexStr := "{" + strconv . Itoa ( groupIndex ) + "}"
2025-04-04 20:27:39 +02:00
s = strings . ReplaceAll ( s , groupIndexStr , groupValue )
2021-05-04 10:05:45 +02:00
}
return s
2017-12-13 11:31:19 -05:00
}
2021-05-04 10:05:45 +02:00
// remapStringRewriter maps a dedicated string to another string
// it also maps a the domain of a sub domain.
type remapStringRewriter struct {
orig string
replacement string
}
var _ stringRewriter = & remapStringRewriter { }
func newRemapStringRewriter ( orig , replacement string ) stringRewriter {
return & remapStringRewriter { orig , replacement }
}
func ( r * remapStringRewriter ) rewriteString ( src string ) string {
if src == r . orig {
return r . replacement
}
plugin/rewrite: drop the per-record dot-prefixed temporary in remapStringRewriter (#8382)
remapStringRewriter matches a record name against orig and its sub domains. The
sub domain check was strings.HasSuffix(src, "."+r.orig), which built the
dot-prefixed string on every call and threw it away. Match the label boundary by
index instead: src is a sub domain of orig when orig sits at the end of src with
a "." immediately before it, which is exactly what the HasSuffix call tested.
Go concatenates short strings into a 32-byte stack buffer, so "."+orig only
reached the heap once orig passed 31 bytes. Below that the temporary was free
and this saves a few ns per record. Above it, 48 B was allocated per record.
Kubernetes service names are past the threshold -
my-service.my-namespace.svc.cluster.local. is 42 bytes - and those are the names
an auto rule rewrites to when a Corefile maps an external name onto an
in-cluster one. orig is the name the question was rewritten to, so whether a
deployment sees the allocation is a property of its Corefile, not its queries.
Caching "."+orig on the rewriter instead does not work: responseRuleFor
constructs a new rewriter for every request an auto name rule rewrites, so the
concatenation would run once per request rather than once per rule, and escapes
to the heap from there. That buys per-record work with a per-request allocation
and regresses every response short enough not to amortize it.
Per record, one rewriteString call on an existing rewriter:
name master this PR
RemapStringRewriter/short/match 186.6n 16 B/1 120.4n 16 B/1 -35%
RemapStringRewriter/short/nomatch 61.5n 0 B/0 16.5n 0 B/0 -73%
RemapStringRewriter/long/match 381.9n 72 B/2 165.3n 24 B/1 -57%
RemapStringRewriter/long/nomatch 219.1n 48 B/1 18.7n 0 B/0 -91%
Per request - rewrite the question, build the response rules, apply them to the
answer:
name master this PR
AutoNameRuleResponse/exact 935n 96 B/4 945n 96 B/4 ~
AutoNameRuleResponse/subdomain 1.248µ 112 B/5 1.242µ 112 B/5 ~
AutoNameRuleResponse/nomatch 1.016µ 96 B/4 945n 96 B/4 -7%
AutoNameRuleResponse/subdomain-8 3.376µ 224 B/12 2.891µ 224 B/12 -14%
AutoNameRuleResponse/k8s/subdomain 1.611µ 176 B/6 1.380µ 128 B/5 -14%
AutoNameRuleResponse/k8s/subdomain-8 5.144µ 672 B/20 3.305µ 288 B/12 -36%
benchstat over 8 runs, i7-1065G7. With short names this is flat at the request
level: one rewriteString call is small next to the four allocations that
building the rules costs. The saving is per record and per byte of name, so it
shows up where responses carry several records and the rewritten-to name is
long.
This applies to exact, prefix, substring and regex name rules with answer auto.
suffix rules build a suffixStringRewriter instead and are not affected.
TestRemapStringRewriter pins the label boundary semantics the index arithmetic
now carries, notably that notexample.com. is not a sub domain of example.com.
It passes against the previous implementation too.
Signed-off-by: Manuel Rüger <manuel@rueg.eu>
2026-08-20 05:27:19 +02:00
// src is a sub domain of orig when orig sits at the end of src with a label
// boundary right before it. Checking that boundary by index matches exactly
// what strings.HasSuffix(src, "."+r.orig) matches, without building the
// dot-prefixed string. Caching that string on the rewriter is not an option:
// responseRuleFor constructs a rewriter per rewritten request, so the cost
// would land on every request to save work on every record.
if i := len ( src ) - len ( r . orig ) ; i > 0 && src [ i - 1 ] == '.' && src [ i : ] == r . orig {
return src [ : i ] + r . replacement
2021-05-04 10:05:45 +02:00
}
return src
}
// suffixStringRewriter maps a dedicated suffix string to another string
type suffixStringRewriter struct {
suffix string
replacement string
}
var _ stringRewriter = & suffixStringRewriter { }
func newSuffixStringRewriter ( orig , replacement string ) stringRewriter {
return & suffixStringRewriter { orig , replacement }
}
func ( r * suffixStringRewriter ) rewriteString ( src string ) string {
2025-10-30 14:07:16 +02:00
if before , ok := strings . CutSuffix ( src , r . suffix ) ; ok {
return before + r . replacement
2021-05-04 10:05:45 +02:00
}
return src
}
// nameRewriterResponseRule maps a record name according to a stringRewriter.
type nameRewriterResponseRule struct {
stringRewriter
}
2026-03-30 03:02:20 +03:00
func ( r * nameRewriterResponseRule ) RewriteResponse ( _res * dns . Msg , rr dns . RR ) {
2021-05-04 10:05:45 +02:00
rr . Header ( ) . Name = r . rewriteString ( rr . Header ( ) . Name )
}
// valueRewriterResponseRule maps a record value according to a stringRewriter.
type valueRewriterResponseRule struct {
stringRewriter
}
2026-03-30 03:02:20 +03:00
func ( r * valueRewriterResponseRule ) RewriteResponse ( _res * dns . Msg , rr dns . RR ) {
2021-05-04 10:05:45 +02:00
value := getRecordValueForRewrite ( rr )
if value != "" {
new := r . rewriteString ( value )
if new != value {
setRewrittenRecordValue ( rr , new )
}
}
2017-12-13 11:31:19 -05:00
}
const (
// ExactMatch matches only on exact match of the name in the question section of a request
ExactMatch = "exact"
// PrefixMatch matches when the name begins with the matching string
PrefixMatch = "prefix"
// SuffixMatch matches when the name ends with the matching string
SuffixMatch = "suffix"
// SubstringMatch matches on partial match of the name in the question section of a request
SubstringMatch = "substring"
// RegexMatch matches when the name in the question section of a request matches a regular expression
RegexMatch = "regex"
2021-05-04 10:05:45 +02:00
// AnswerMatch matches an answer rewrite
AnswerMatch = "answer"
// AutoMatch matches the auto name answer rewrite
AutoMatch = "auto"
// NameMatch matches the name answer rewrite
NameMatch = "name"
// ValueMatch matches the value answer rewrite
ValueMatch = "value"
2017-12-13 11:31:19 -05:00
)
2021-05-04 10:05:45 +02:00
type nameRuleBase struct {
nextAction string
auto bool
replacement string
static ResponseRules
}
func newNameRuleBase ( nextAction string , auto bool , replacement string , staticResponses ResponseRules ) nameRuleBase {
return nameRuleBase {
nextAction : nextAction ,
auto : auto ,
replacement : replacement ,
static : staticResponses ,
}
}
// responseRuleFor create for auto mode dynamically response rewriters for name and value
// reverting the mapping done by the name rewrite rule, which can be found in the state.
func ( rule * nameRuleBase ) responseRuleFor ( state request . Request ) ( ResponseRules , Result ) {
if ! rule . auto {
return rule . static , RewriteDone
}
rewriter := newRemapStringRewriter ( state . Req . Question [ 0 ] . Name , state . Name ( ) )
2026-03-06 21:50:24 +02:00
rules := make ( ResponseRules , 0 , 2 + len ( rule . static ) )
rules = append ( rules ,
2021-05-04 10:05:45 +02:00
& nameRewriterResponseRule { rewriter } ,
& valueRewriterResponseRule { rewriter } ,
2026-03-06 21:50:24 +02:00
)
2021-05-04 10:05:45 +02:00
return append ( rules , rule . static ... ) , RewriteDone
}
// Mode returns the processing nextAction
func ( rule * nameRuleBase ) Mode ( ) string { return rule . nextAction }
// exactNameRule rewrites the current request based upon exact match of the name
2018-07-02 15:39:50 +01:00
// in the question section of the request.
2021-05-04 10:05:45 +02:00
type exactNameRule struct {
nameRuleBase
from string
}
func newExactNameRule ( nextAction string , orig , replacement string , answers ResponseRules ) Rule {
return & exactNameRule {
newNameRuleBase ( nextAction , true , replacement , answers ) ,
orig ,
}
}
2026-03-30 03:02:20 +03:00
func ( rule * exactNameRule ) Rewrite ( _ctx context . Context , state request . Request ) ( ResponseRules , Result ) {
2021-05-04 10:05:45 +02:00
if rule . from == state . Name ( ) {
state . Req . Question [ 0 ] . Name = rule . replacement
return rule . responseRuleFor ( state )
2017-02-07 16:53:16 -05:00
}
2021-05-04 10:05:45 +02:00
return nil , RewriteIgnored
}
// prefixNameRule rewrites the current request when the name begins with the matching string.
type prefixNameRule struct {
nameRuleBase
prefix string
2017-02-07 16:53:16 -05:00
}
2017-09-20 13:06:53 -07:00
2021-05-04 10:05:45 +02:00
func newPrefixNameRule ( nextAction string , auto bool , prefix , replacement string , answers ResponseRules ) Rule {
return & prefixNameRule {
newNameRuleBase ( nextAction , auto , replacement , answers ) ,
prefix ,
2017-12-13 11:31:19 -05:00
}
}
2026-03-30 03:02:20 +03:00
func ( rule * prefixNameRule ) Rewrite ( _ctx context . Context , state request . Request ) ( ResponseRules , Result ) {
2025-09-10 23:08:27 +03:00
if after , ok := strings . CutPrefix ( state . Name ( ) , rule . prefix ) ; ok {
state . Req . Question [ 0 ] . Name = rule . replacement + after
2021-05-04 10:05:45 +02:00
return rule . responseRuleFor ( state )
2017-12-13 11:31:19 -05:00
}
2021-05-04 10:05:45 +02:00
return nil , RewriteIgnored
2017-12-13 11:31:19 -05:00
}
2021-05-04 10:05:45 +02:00
// suffixNameRule rewrites the current request when the name ends with the matching string.
type suffixNameRule struct {
nameRuleBase
suffix string
}
func newSuffixNameRule ( nextAction string , auto bool , suffix , replacement string , answers ResponseRules ) Rule {
2026-03-06 21:50:24 +02:00
rules := make ( ResponseRules , 0 , len ( answers ) )
2021-05-04 10:05:45 +02:00
if auto {
// for a suffix rewriter better standard response rewrites can be done
// just by using the original suffix/replacement in the opposite order
rewriter := newSuffixStringRewriter ( replacement , suffix )
2026-03-06 21:50:24 +02:00
rules = make ( ResponseRules , 0 , 2 + len ( answers ) )
rules = append ( rules ,
2021-05-04 10:05:45 +02:00
& nameRewriterResponseRule { rewriter } ,
& valueRewriterResponseRule { rewriter } ,
2026-03-06 21:50:24 +02:00
)
2021-05-04 10:05:45 +02:00
}
return & suffixNameRule {
newNameRuleBase ( nextAction , false , replacement , append ( rules , answers ... ) ) ,
suffix ,
}
}
2026-03-30 03:02:20 +03:00
func ( rule * suffixNameRule ) Rewrite ( _ctx context . Context , state request . Request ) ( ResponseRules , Result ) {
2025-10-30 14:07:16 +02:00
if before , ok := strings . CutSuffix ( state . Name ( ) , rule . suffix ) ; ok {
state . Req . Question [ 0 ] . Name = before + rule . replacement
2021-05-04 10:05:45 +02:00
return rule . responseRuleFor ( state )
}
return nil , RewriteIgnored
}
// substringNameRule rewrites the current request based upon partial match of the
2018-07-02 15:39:50 +01:00
// name in the question section of the request.
2021-05-04 10:05:45 +02:00
type substringNameRule struct {
nameRuleBase
substring string
}
func newSubstringNameRule ( nextAction string , auto bool , substring , replacement string , answers ResponseRules ) Rule {
return & substringNameRule {
newNameRuleBase ( nextAction , auto , replacement , answers ) ,
substring ,
}
}
2026-03-30 03:02:20 +03:00
func ( rule * substringNameRule ) Rewrite ( _ctx context . Context , state request . Request ) ( ResponseRules , Result ) {
2021-05-04 10:05:45 +02:00
if strings . Contains ( state . Name ( ) , rule . substring ) {
2025-04-04 20:27:39 +02:00
state . Req . Question [ 0 ] . Name = strings . ReplaceAll ( state . Name ( ) , rule . substring , rule . replacement )
2021-05-04 10:05:45 +02:00
return rule . responseRuleFor ( state )
2017-12-13 11:31:19 -05:00
}
2021-05-04 10:05:45 +02:00
return nil , RewriteIgnored
2017-12-13 11:31:19 -05:00
}
2021-05-04 10:05:45 +02:00
// regexNameRule rewrites the current request when the name in the question
2018-07-02 15:39:50 +01:00
// section of the request matches a regular expression.
2021-05-04 10:05:45 +02:00
type regexNameRule struct {
nameRuleBase
pattern * regexp . Regexp
}
func newRegexNameRule ( nextAction string , auto bool , pattern * regexp . Regexp , replacement string , answers ResponseRules ) Rule {
return & regexNameRule {
newNameRuleBase ( nextAction , auto , replacement , answers ) ,
pattern ,
}
}
2026-03-30 03:02:20 +03:00
func ( rule * regexNameRule ) Rewrite ( _ctx context . Context , state request . Request ) ( ResponseRules , Result ) {
2021-05-04 10:05:45 +02:00
regexGroups := rule . pattern . FindStringSubmatch ( state . Name ( ) )
2017-12-13 11:31:19 -05:00
if len ( regexGroups ) == 0 {
2021-05-04 10:05:45 +02:00
return nil , RewriteIgnored
2017-12-13 11:31:19 -05:00
}
2021-05-04 10:05:45 +02:00
s := rule . replacement
2017-12-13 11:31:19 -05:00
for groupIndex , groupValue := range regexGroups {
groupIndexStr := "{" + strconv . Itoa ( groupIndex ) + "}"
2025-04-04 20:27:39 +02:00
s = strings . ReplaceAll ( s , groupIndexStr , groupValue )
2017-12-13 11:31:19 -05:00
}
2018-07-02 15:39:50 +01:00
state . Req . Question [ 0 ] . Name = s
2021-05-04 10:05:45 +02:00
return rule . responseRuleFor ( state )
2017-12-13 11:31:19 -05:00
}
// newNameRule creates a name matching rule based on exact, partial, or regex match
func newNameRule ( nextAction string , args ... string ) ( Rule , error ) {
2018-10-23 16:59:59 -04:00
var matchType , rewriteQuestionFrom , rewriteQuestionTo string
2017-12-13 11:31:19 -05:00
if len ( args ) < 2 {
return nil , fmt . Errorf ( "too few arguments for a name rule" )
}
2018-10-23 16:59:59 -04:00
if len ( args ) == 2 {
2022-03-01 11:33:52 -08:00
matchType = ExactMatch
2018-10-23 16:59:59 -04:00
rewriteQuestionFrom = plugin . Name ( args [ 0 ] ) . Normalize ( )
rewriteQuestionTo = plugin . Name ( args [ 1 ] ) . Normalize ( )
}
if len ( args ) >= 3 {
matchType = strings . ToLower ( args [ 0 ] )
2022-03-01 11:33:52 -08:00
if matchType == RegexMatch {
rewriteQuestionFrom = args [ 1 ]
rewriteQuestionTo = args [ 2 ]
} else {
rewriteQuestionFrom = plugin . Name ( args [ 1 ] ) . Normalize ( )
rewriteQuestionTo = plugin . Name ( args [ 2 ] ) . Normalize ( )
}
2018-10-23 16:59:59 -04:00
}
if matchType == ExactMatch || matchType == SuffixMatch {
if ! hasClosingDot ( rewriteQuestionFrom ) {
rewriteQuestionFrom = rewriteQuestionFrom + "."
}
if ! hasClosingDot ( rewriteQuestionTo ) {
rewriteQuestionTo = rewriteQuestionTo + "."
}
}
2021-05-04 10:05:45 +02:00
var err error
var answers ResponseRules
auto := false
if len ( args ) > 3 {
auto , answers , err = parseAnswerRules ( matchType , args [ 3 : ] )
if err != nil {
return nil , err
}
2018-10-23 16:59:59 -04:00
}
2021-05-04 10:05:45 +02:00
switch matchType {
case ExactMatch :
if _ , err := isValidRegexPattern ( rewriteQuestionTo , rewriteQuestionFrom ) ; err != nil {
return nil , err
}
return newExactNameRule ( nextAction , rewriteQuestionFrom , rewriteQuestionTo , answers ) , nil
case PrefixMatch :
return newPrefixNameRule ( nextAction , auto , rewriteQuestionFrom , rewriteQuestionTo , answers ) , nil
case SuffixMatch :
return newSuffixNameRule ( nextAction , auto , rewriteQuestionFrom , rewriteQuestionTo , answers ) , nil
case SubstringMatch :
return newSubstringNameRule ( nextAction , auto , rewriteQuestionFrom , rewriteQuestionTo , answers ) , nil
case RegexMatch :
rewriteQuestionFromPattern , err := isValidRegexPattern ( rewriteQuestionFrom , rewriteQuestionTo )
if err != nil {
return nil , err
2017-12-13 11:31:19 -05:00
}
2021-05-04 10:05:45 +02:00
rewriteQuestionTo := plugin . Name ( args [ 2 ] ) . Normalize ( )
return newRegexNameRule ( nextAction , auto , rewriteQuestionFromPattern , rewriteQuestionTo , answers ) , nil
default :
return nil , fmt . Errorf ( "name rule supports only exact, prefix, suffix, substring, and regex name matching, received: %s" , matchType )
}
}
func parseAnswerRules ( name string , args [ ] string ) ( auto bool , rules ResponseRules , err error ) {
auto = false
arg := 0
nameRules := 0
last := ""
if len ( args ) < 2 {
return false , nil , fmt . Errorf ( "invalid arguments for %s rule" , name )
2017-12-13 11:31:19 -05:00
}
2021-05-04 10:05:45 +02:00
for arg < len ( args ) {
if last == "" && args [ arg ] != AnswerMatch {
2026-05-22 03:11:29 +08:00
return false , nil , fmt . Errorf ( "exceeded the number of arguments for a non-answer rule argument for %s rule" , name )
2021-05-04 10:05:45 +02:00
}
if args [ arg ] == AnswerMatch {
arg ++
}
if len ( args ) - arg == 0 {
return false , nil , fmt . Errorf ( "type missing for answer rule for %s rule" , name )
}
last = args [ arg ]
arg ++
switch last {
case AutoMatch :
auto = true
continue
case NameMatch :
if len ( args ) - arg < 2 {
return false , nil , fmt . Errorf ( "%s answer rule for %s rule: 2 arguments required" , last , name )
}
rewriteAnswerFrom := args [ arg ]
rewriteAnswerTo := args [ arg + 1 ]
rewriteAnswerFromPattern , err := isValidRegexPattern ( rewriteAnswerFrom , rewriteAnswerTo )
rewriteAnswerTo = plugin . Name ( rewriteAnswerTo ) . Normalize ( )
2018-10-23 16:59:59 -04:00
if err != nil {
2021-05-04 10:05:45 +02:00
return false , nil , fmt . Errorf ( "%s answer rule for %s rule: %s" , last , name , err )
2018-10-23 16:59:59 -04:00
}
2021-05-04 10:05:45 +02:00
rules = append ( rules , & nameRewriterResponseRule { newStringRewriter ( rewriteAnswerFromPattern , rewriteAnswerTo ) } )
arg += 2
nameRules ++
case ValueMatch :
if len ( args ) - arg < 2 {
return false , nil , fmt . Errorf ( "%s answer rule for %s rule: 2 arguments required" , last , name )
2021-02-23 09:12:40 +00:00
}
2021-05-04 10:05:45 +02:00
rewriteAnswerFrom := args [ arg ]
rewriteAnswerTo := args [ arg + 1 ]
rewriteAnswerFromPattern , err := isValidRegexPattern ( rewriteAnswerFrom , rewriteAnswerTo )
rewriteAnswerTo = plugin . Name ( rewriteAnswerTo ) . Normalize ( )
if err != nil {
return false , nil , fmt . Errorf ( "%s answer rule for %s rule: %s" , last , name , err )
}
rules = append ( rules , & valueRewriterResponseRule { newStringRewriter ( rewriteAnswerFromPattern , rewriteAnswerTo ) } )
arg += 2
default :
return false , nil , fmt . Errorf ( "invalid type %q for answer rule for %s rule" , last , name )
2018-01-18 10:41:14 -05:00
}
}
2017-12-13 11:31:19 -05:00
2021-05-04 10:05:45 +02:00
if auto && nameRules > 0 {
return false , nil , fmt . Errorf ( "auto name answer rule cannot be combined with explicit name anwer rules" )
2021-02-23 09:12:40 +00:00
}
2025-09-21 18:17:35 +03:00
return auto , rules , nil
2021-02-24 10:22:15 +00:00
}
2018-01-18 10:41:14 -05:00
2020-09-01 15:10:45 +08:00
// hasClosingDot returns true if s has a closing dot at the end.
2018-10-23 16:59:59 -04:00
func hasClosingDot ( s string ) bool {
2019-09-27 18:10:34 +08:00
return strings . HasSuffix ( s , "." )
2018-10-23 16:59:59 -04:00
}
2020-09-01 15:10:45 +08:00
// getSubExprUsage returns the number of subexpressions used in s.
2018-10-23 16:59:59 -04:00
func getSubExprUsage ( s string ) int {
subExprUsage := 0
2025-05-29 03:50:55 +03:00
for i := range 101 {
2018-10-23 16:59:59 -04:00
if strings . Contains ( s , "{" + strconv . Itoa ( i ) + "}" ) {
subExprUsage ++
}
}
return subExprUsage
}
2020-09-01 15:10:45 +08:00
// isValidRegexPattern returns a regular expression for pattern matching or errors, if any.
2018-10-23 16:59:59 -04:00
func isValidRegexPattern ( rewriteFrom , rewriteTo string ) ( * regexp . Regexp , error ) {
2026-01-05 19:48:48 +02:00
if len ( rewriteFrom ) > maxRegexpLen {
return nil , fmt . Errorf ( "regex pattern too long: %d > %d" , len ( rewriteFrom ) , maxRegexpLen )
}
2018-10-23 16:59:59 -04:00
rewriteFromPattern , err := regexp . Compile ( rewriteFrom )
if err != nil {
2019-02-27 20:25:02 +07:00
return nil , fmt . Errorf ( "invalid regex matching pattern: %s" , rewriteFrom )
2018-10-23 16:59:59 -04:00
}
if getSubExprUsage ( rewriteTo ) > rewriteFromPattern . NumSubexp ( ) {
2019-02-27 20:25:02 +07:00
return nil , fmt . Errorf ( "the rewrite regex pattern (%s) uses more subexpressions than its corresponding matching regex pattern (%s)" , rewriteTo , rewriteFrom )
2018-10-23 16:59:59 -04:00
}
return rewriteFromPattern , nil
}