@@ -42,19 +42,18 @@ type Session struct {
4242func NewClient (ctx context.Context , cfg * cli.Config ) (* Session , error ) {
4343 anc := & Session {
4444 Client : & http.Client {
45- Transport : urlRewriter {
46- RoundTripper : responseChecker {
47- RoundTripper : userAgentSetter {
48- RoundTripper : preferSetter {
49- cfg : cfg ,
50- RoundTripper : new (http.Transport ),
51- },
52- },
45+ Transport : Middlewares {
46+ urlRewriter {
47+ url : cfg .API .URL ,
5348 },
54- URL : cfg .API .URL ,
55- },
49+ responseChecker ,
50+ userAgentSetter ,
51+ preferSetter {
52+ cfg : cfg ,
53+ },
54+ autoRetrier ,
55+ }.RoundTripper (new (http.Transport )),
5656 },
57-
5857 cfg : cfg ,
5958 }
6059
@@ -271,12 +270,12 @@ func getOrgServicesPath(orgSlug string) string {
271270 return "/orgs/" + url .QueryEscape (orgSlug ) + "/services"
272271}
273272
274- func (s * Session ) GetOrgServices (ctx context.Context , orgSlug string ) ([]Service , error ) {
273+ func (s * Session ) GetOrgServices (ctx context.Context , orgSlug string , filters ... Filter [ Service ] ) ([]Service , error ) {
275274 var svc Services
276275 if err := s .get (ctx , getOrgServicesPath (orgSlug ), & svc ); err != nil {
277276 return nil , err
278277 }
279- return svc .Items , nil
278+ return Filters [ Service ]( filters ). Apply ( svc .Items ) , nil
280279}
281280
282281func getServicePath (orgSlug , serviceSlug string ) string {
@@ -361,90 +360,132 @@ func (r basicAuther) RoundTrip(req *http.Request) (*http.Response, error) {
361360 return r .RoundTripper .RoundTrip (req )
362361}
363362
364- type responseChecker struct {
365- http.RoundTripper
363+ type Middleware interface {
364+ RoundTripper (next http.RoundTripper ) http.RoundTripper
365+ }
366+
367+ type Middlewares []Middleware
368+
369+ func (m Middlewares ) RoundTripper (tport * http.Transport ) http.RoundTripper {
370+ rm := slices .Clone (m )
371+ slices .Reverse (rm )
372+
373+ var next http.RoundTripper = tport
374+ for _ , mw := range rm {
375+ next = mw .RoundTripper (next )
376+ }
377+ return next
378+ }
379+
380+ type RoundTripFunc func (* http.Request ) (* http.Response , error )
381+
382+ func (fn RoundTripFunc ) RoundTrip (req * http.Request ) (* http.Response , error ) {
383+ return fn (req )
384+ }
385+
386+ type MiddlewareFunc func (next http.RoundTripper ) http.RoundTripper
387+
388+ func (fn MiddlewareFunc ) RoundTripper (next http.RoundTripper ) http.RoundTripper {
389+ return fn (next )
366390}
367391
368392var jsonMediaTypes = mediaTypes {
369393 "application/json" ,
370394 "application/problem+json" ,
371395}
372396
373- func (r responseChecker ) RoundTrip (req * http.Request ) (* http.Response , error ) {
374- res , err := r .RoundTripper .RoundTrip (req )
375- if err != nil {
376- return nil , fmt .Errorf ("request error %s %s: %w" , req .Method , req .URL .Path , err )
377- }
397+ var responseChecker = MiddlewareFunc (func (next http.RoundTripper ) http.RoundTripper {
398+ return RoundTripFunc (func (req * http.Request ) (* http.Response , error ) {
399+ res , err := next .RoundTrip (req )
400+ if err != nil {
401+ return nil , fmt .Errorf ("request error %s %s: %w" , req .Method , req .URL .Path , err )
402+ }
378403
379- requestId := res .Header .Get ("X-Request-Id" )
404+ requestId := res .Header .Get ("X-Request-Id" )
380405
381- switch res .StatusCode {
382- case http .StatusForbidden :
383- return nil , ErrSignedOut
384- case http .StatusInternalServerError :
385- return nil , fmt .Errorf ("request [%s] failed: 500 Internal Server Error" , requestId )
386- }
387- if contentType := res .Header .Get ("Content-Type" ); ! jsonMediaTypes .Matches (contentType ) {
388- return nil , fmt .Errorf ("request [%s]: %d response, expected json content-type, got: %q" , requestId , res .StatusCode , contentType )
389- }
390- return res , nil
391- }
406+ switch res .StatusCode {
407+ case http .StatusForbidden :
408+ return nil , ErrSignedOut
409+ case http .StatusInternalServerError :
410+ return nil , fmt .Errorf ("request [%s] failed: 500 Internal Server Error" , requestId )
411+ }
412+ if contentType := res .Header .Get ("Content-Type" ); ! jsonMediaTypes .Matches (contentType ) {
413+ return nil , fmt .Errorf ("request [%s]: %d response, expected json content-type, got: %q" , requestId , res .StatusCode , contentType )
414+ }
415+ return res , nil
416+ })
417+ })
392418
393419type urlRewriter struct {
394- http.RoundTripper
395-
396- URL string
420+ url string
397421}
398422
399- func (r urlRewriter ) RoundTrip (req * http.Request ) (* http.Response , error ) {
400- u , err := url .Parse (r .URL )
401- if err != nil {
402- return nil , err
403- }
404- req .URL = u .JoinPath (req .URL .Path )
423+ func (r urlRewriter ) RoundTripper (next http.RoundTripper ) http.RoundTripper {
424+ return RoundTripFunc (func (req * http.Request ) (* http.Response , error ) {
425+ u , err := url .Parse (r .url )
426+ if err != nil {
427+ return nil , err
428+ }
429+ req .URL = u .JoinPath (req .URL .Path )
405430
406- return r .RoundTripper .RoundTrip (req )
431+ return next .RoundTrip (req )
432+ })
407433}
408434
409435type preferSetter struct {
410- http.RoundTripper
411-
412436 cfg * cli.Config
413437}
414438
415- func (s preferSetter ) RoundTrip (req * http.Request ) (* http.Response , error ) {
416- path := req .URL .Path
439+ func (s preferSetter ) RoundTripper (next http.RoundTripper ) http.RoundTripper {
440+ return RoundTripFunc (func (req * http.Request ) (* http.Response , error ) {
441+ path := req .URL .Path
417442
418- var value []string
443+ var value []string
419444
420- if s .cfg .Test .Prefer [path ].Code != 0 {
421- value = append (value , fmt .Sprintf ("code=%d" , s .cfg .Test .Prefer [path ].Code ))
422- }
445+ if s .cfg .Test .Prefer [path ].Code != 0 {
446+ value = append (value , fmt .Sprintf ("code=%d" , s .cfg .Test .Prefer [path ].Code ))
447+ }
423448
424- if s .cfg .Test .Prefer [path ].Dynamic {
425- value = append (value , fmt .Sprintf ("dynamic=%t" , s .cfg .Test .Prefer [path ].Dynamic ))
426- }
449+ if s .cfg .Test .Prefer [path ].Dynamic {
450+ value = append (value , fmt .Sprintf ("dynamic=%t" , s .cfg .Test .Prefer [path ].Dynamic ))
451+ }
427452
428- if s .cfg .Test .Prefer [path ].Example != "" {
429- value = append (value , fmt .Sprintf ("example=%s" , s .cfg .Test .Prefer [path ].Example ))
430- }
453+ if s .cfg .Test .Prefer [path ].Example != "" {
454+ value = append (value , fmt .Sprintf ("example=%s" , s .cfg .Test .Prefer [path ].Example ))
455+ }
431456
432- if len (value ) > 0 {
433- req .Header .Set ("Prefer" , strings .Join (value , " " ))
434- }
457+ if len (value ) > 0 {
458+ req .Header .Set ("Prefer" , strings .Join (value , " " ))
459+ }
435460
436- return s .RoundTripper .RoundTrip (req )
461+ return next .RoundTrip (req )
462+ })
437463}
438464
439- type userAgentSetter struct {
440- http.RoundTripper
441- }
465+ var userAgentSetter = MiddlewareFunc ( func ( next http. RoundTripper ) http. RoundTripper {
466+ return RoundTripFunc ( func ( req * http.Request ) ( * http. Response , error ) {
467+ req . Header . Set ( "User-Agent" , cli . UserAgent ())
442468
443- func (s userAgentSetter ) RoundTrip (req * http.Request ) (* http.Response , error ) {
444- req .Header .Set ("User-Agent" , cli .UserAgent ())
469+ return next .RoundTrip (req )
470+ })
471+ })
445472
446- return s .RoundTripper .RoundTrip (req )
447- }
473+ var autoRetrier = MiddlewareFunc (func (next http.RoundTripper ) http.RoundTripper {
474+ return RoundTripFunc (func (req * http.Request ) (* http.Response , error ) {
475+ res , err := next .RoundTrip (req )
476+ if res == nil {
477+ return res , err
478+ }
479+
480+ switch res .StatusCode {
481+ case http .StatusBadGateway , http .StatusServiceUnavailable :
482+ // TODO: configure a backoff/sleep here?
483+ return next .RoundTrip (req )
484+ default :
485+ return res , err
486+ }
487+ })
488+ })
448489
449490type mediaTypes []string
450491
@@ -472,3 +513,14 @@ func gnomeKeyringMissing(cfg *cli.Config) bool {
472513 }
473514 return true
474515}
516+
517+ type Filter [T any ] func (s []T ) []T
518+
519+ type Filters [T any ] []Filter [T ]
520+
521+ func (f Filters [T ]) Apply (s []T ) []T {
522+ for _ , fn := range f {
523+ s = fn (s )
524+ }
525+ return s
526+ }
0 commit comments