@@ -3,6 +3,7 @@ package internal
33import (
44 "encoding/json"
55 "fmt"
6+ "strings"
67 "time"
78)
89
@@ -11,20 +12,14 @@ import (
1112type Duration string
1213
1314// UnmarshalJSON implements the json.Unmarshaller interface.
14- // Negative durations are rejected because timeout values must be
15- // zero (disabled) or positive.
1615func (d * Duration ) UnmarshalJSON (b []byte ) error {
1716 var str string
1817 if err := json .Unmarshal (b , & str ); err != nil {
1918 return err
2019 }
21- pd , err := time .ParseDuration (str )
22- if err != nil {
20+ if _ , err := time .ParseDuration (str ); err != nil {
2321 return fmt .Errorf ("invalid duration: %s" , str )
2422 }
25- if pd < 0 {
26- return fmt .Errorf ("negative duration not allowed: %s" , str )
27- }
2823 * d = Duration (str )
2924 return nil
3025}
@@ -44,6 +39,30 @@ func (d *Duration) TimeDuration() (*time.Duration, error) {
4439 return & pd , nil
4540}
4641
42+ // nonNegative requires the duration to be zero or positive.
43+ func nonNegative (d Duration ) bool {
44+ return ! strings .HasPrefix (string (d ), "-" )
45+ }
46+
47+ // nonNegativeOrMinusOne requires the duration to be zero, positive,
48+ // or -1 (to disable keep-alive probes).
49+ func nonNegativeOrMinusOne (d Duration ) bool {
50+ return ! strings .HasPrefix (string (d ), "-" ) || strings .HasPrefix (string (d ), "-1" )
51+ }
52+
53+ func validateDuration (name string , d * Duration , valid func (Duration ) bool ) error {
54+ if d == nil {
55+ return nil
56+ }
57+ if _ , err := d .TimeDuration (); err != nil {
58+ return err
59+ }
60+ if ! valid (* d ) {
61+ return fmt .Errorf ("invalid value for %s: %s" , name , string (* d ))
62+ }
63+ return nil
64+ }
65+
4766// HTTPSettings contains the timeout settings for HTTP clients.
4867// All timeout values use Duration (Go duration strings in config).
4968// If not set (nil), the http.DefaultTransport value from the Go
@@ -63,6 +82,7 @@ type HTTPSettings struct {
6382 TCPDialTimeout * Duration `json:"tcpDialTimeout,omitempty"`
6483
6584 // TCPKeepAlive is the interval between TCP keep-alive probes.
85+ // Use -1 to disable keep-alive probes.
6686 TCPKeepAlive * Duration `json:"tcpKeepAlive,omitempty"`
6787
6888 // TLSHandshakeTimeout is the maximum time to wait for a TLS handshake.
@@ -74,3 +94,26 @@ type HTTPSettings struct {
7494 // IdleConnTimeout is the maximum time an idle connection remains open.
7595 IdleConnTimeout * Duration `json:"idleConnTimeout,omitempty"`
7696}
97+
98+ // Validate checks that timeout values are non-negative.
99+ // TCPKeepAlive additionally allows -1 to disable keep-alive probes
100+ // (consistent with Go's net.Dialer.KeepAlive).
101+ func (s * HTTPSettings ) Validate () error {
102+ for _ , check := range []struct {
103+ name string
104+ val * Duration
105+ valid func (Duration ) bool
106+ }{
107+ {"timeout" , s .Timeout , nonNegative },
108+ {"tcpDialTimeout" , s .TCPDialTimeout , nonNegative },
109+ {"tcpKeepAlive" , s .TCPKeepAlive , nonNegativeOrMinusOne },
110+ {"tlsHandshakeTimeout" , s .TLSHandshakeTimeout , nonNegative },
111+ {"responseHeaderTimeout" , s .ResponseHeaderTimeout , nonNegative },
112+ {"idleConnTimeout" , s .IdleConnTimeout , nonNegative },
113+ } {
114+ if err := validateDuration (check .name , check .val , check .valid ); err != nil {
115+ return err
116+ }
117+ }
118+ return nil
119+ }
0 commit comments