@@ -19,6 +19,7 @@ import (
1919 "io"
2020 "net/http"
2121 "os"
22+ "slices"
2223 "strconv"
2324 "strings"
2425 "text/template"
@@ -71,14 +72,32 @@ type HTTPTargetConfig struct {
7172
7273// ResponseRules is part of HTTP target configuration. It provides rules how HTTP respones should be handled. Response can be categerized as 'invalid' (bad data), as setup error or (if none of the rules matches) as a transient error.
7374type ResponseRules struct {
74- Invalid []Rule `hcl:"invalid,block"`
75- SetupError []Rule `hcl:"setup,block"`
75+ Rules []Rule `hcl:"rule,block"`
76+ // Invalid []Rule `hcl:"invalid,block"`
77+ // SetupError []Rule `hcl:"setup,block"`
78+ }
79+
80+ type ResponseRuleType string
81+
82+ const (
83+ ResponseRuleTypeInvalid ResponseRuleType = "invalid"
84+ ResponseRuleTypeSetup ResponseRuleType = "setup"
85+ )
86+
87+ func isValidResponseRuleType (ruleType ResponseRuleType ) bool {
88+ switch ruleType {
89+ case ResponseRuleTypeInvalid , ResponseRuleTypeSetup :
90+ return true
91+ default :
92+ return false
93+ }
7694}
7795
7896// Rule configuration defines what kind of values are expected to exist in HTTP response, like status code or message in the body.
7997type Rule struct {
80- MatchingHTTPCodes []int `hcl:"http_codes,optional"`
81- MatchingBodyPart string `hcl:"body,optional"`
98+ Type ResponseRuleType `hcl:"type,optional"`
99+ MatchingHTTPCodes []int `hcl:"http_codes,optional"`
100+ MatchingBodyPart string `hcl:"body,optional"`
82101}
83102
84103// Helper struct storing response HTTP status code and parsed response body
@@ -244,6 +263,15 @@ func HTTPTargetConfigFunction(c *HTTPTargetConfig) (*HTTPTarget, error) {
244263 return nil , errors .New ("target error: Byte limit must be larger than template size" )
245264 }
246265
266+ // validating response rules from config
267+ if c .ResponseRules != nil {
268+ for _ , rule := range c .ResponseRules .Rules {
269+ if ! isValidResponseRuleType (rule .Type ) {
270+ return nil , fmt .Errorf ("target error: Invalid response rule type '%s'. Valid types are: 'invalid', 'setup'" , rule .Type )
271+ }
272+ }
273+ }
274+
247275 return & HTTPTarget {
248276 client : client ,
249277 httpURL : c .URL ,
@@ -289,8 +317,7 @@ func defaultConfiguration() *HTTPTargetConfig {
289317
290318 ContentType : "application/json" ,
291319 ResponseRules : & ResponseRules {
292- Invalid : []Rule {},
293- SetupError : []Rule {},
320+ Rules : []Rule {},
294321 },
295322 IncludeTimingHeaders : false ,
296323 RejectionThresholdInMillis : 150 ,
@@ -420,42 +447,53 @@ func (ht *HTTPTarget) Write(messages []*models.Message) (*models.TargetWriteResu
420447 })
421448 }
422449
423- if matchedRule := findMatchingRule (response , ht .responseRules .Invalid ); matchedRule != nil {
424- for _ , msg := range goodMsgs {
425- msg .SetError (& models.ApiError {
426- StatusCode : resp .Status ,
427- ResponseBody : response .Body ,
428- SafeMessage : "Invalid error" ,
429- })
450+ // Find first matching rule in order
451+ var matchedRule * Rule
452+ for _ , rule := range ht .responseRules .Rules {
453+ if ruleMatches (response , rule ) {
454+ matchedRule = & rule
455+ break
430456 }
431-
432- invalid = append (invalid , goodMsgs ... )
433- continue
434457 }
435458
436- var errorDetails error
437- if rule := findMatchingRule (response , ht .responseRules .SetupError ); rule != nil {
438- hitSetupError = true
439-
440- if rule .MatchingBodyPart != "" {
441- errorDetails = fmt .Errorf ("got setup error, response status: '%s' with error details: '%s'" , resp .Status , rule .MatchingBodyPart )
442- } else {
443- errorDetails = fmt .Errorf ("got setup error, response status: '%s'" , resp .Status )
444- }
459+ if matchedRule != nil {
460+ switch matchedRule .Type {
461+ case ResponseRuleTypeInvalid :
462+ for _ , msg := range goodMsgs {
463+ msg .SetError (& models.ApiError {
464+ StatusCode : resp .Status ,
465+ ResponseBody : response .Body ,
466+ SafeMessage : "Invalid error" ,
467+ })
468+ }
469+ invalid = append (invalid , goodMsgs ... )
470+ continue
471+
472+ case ResponseRuleTypeSetup :
473+ hitSetupError = true
474+ var errorDetails error
475+ if matchedRule .MatchingBodyPart != "" {
476+ errorDetails = fmt .Errorf ("got setup error, response status: '%s' with error details: '%s'" , resp .Status , matchedRule .MatchingBodyPart )
477+ } else {
478+ errorDetails = fmt .Errorf ("got setup error, response status: '%s'" , resp .Status )
479+ }
445480
446- for _ , msg := range goodMsgs {
447- msg .SetError (& models.ApiError {
448- StatusCode : resp .Status ,
449- ResponseBody : response .Body ,
450- SafeMessage : "Setup error" ,
451- })
481+ for _ , msg := range goodMsgs {
482+ msg .SetError (& models.ApiError {
483+ StatusCode : resp .Status ,
484+ ResponseBody : response .Body ,
485+ SafeMessage : "Setup error" ,
486+ })
487+ }
488+ errResult = multierror .Append (errResult , errorDetails )
489+ failed = append (failed , goodMsgs ... )
452490 }
453-
454491 } else {
455- errorDetails = fmt .Errorf ("got transient error, response status: '%s'" , resp .Status )
492+ // No rule matched - transient error
493+ errorDetails := fmt .Errorf ("got transient error, response status: '%s'" , resp .Status )
494+ errResult = multierror .Append (errResult , errorDetails )
495+ failed = append (failed , goodMsgs ... )
456496 }
457- errResult = multierror .Append (errResult , errorDetails )
458- failed = append (failed , goodMsgs ... )
459497 }
460498 }
461499
@@ -467,15 +505,6 @@ func (ht *HTTPTarget) Write(messages []*models.Message) (*models.TargetWriteResu
467505 return models .NewTargetWriteResult (sent , failed , oversized , invalid ), errResult
468506}
469507
470- func findMatchingRule (res response , rules []Rule ) * Rule {
471- for _ , rule := range rules {
472- if ruleMatches (res , rule ) {
473- return & rule
474- }
475- }
476- return nil
477- }
478-
479508func ruleMatches (res response , rule Rule ) bool {
480509 codeMatch := httpStatusMatches (res .Status , rule .MatchingHTTPCodes )
481510 if rule .MatchingBodyPart != "" {
@@ -485,12 +514,7 @@ func ruleMatches(res response, rule Rule) bool {
485514}
486515
487516func httpStatusMatches (actual int , expectedCodes []int ) bool {
488- for _ , expected := range expectedCodes {
489- if expected == actual {
490- return true
491- }
492- }
493- return false
517+ return slices .Contains (expectedCodes , actual )
494518}
495519
496520func responseBodyMatches (actual string , bodyPattern string ) bool {
0 commit comments