//go:build go1.18 // +build go1.18 // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package shared import ( "context" "errors" "fmt" "io" "net/http" "reflect" "regexp" "strconv" "strings" "time" ) // CtxWithHTTPHeaderKey is used as a context key for adding/retrieving http.Header. type CtxWithHTTPHeaderKey struct{} // CtxWithRetryOptionsKey is used as a context key for adding/retrieving RetryOptions. type CtxWithRetryOptionsKey struct{} // CtxIncludeResponseKey is used as a context key for retrieving the raw response. type CtxIncludeResponseKey struct{} // Delay waits for the duration to elapse or the context to be cancelled. func Delay(ctx context.Context, delay time.Duration) error { select { case <-time.After(delay): return nil case <-ctx.Done(): return ctx.Err() } } // RetryAfter returns non-zero if the response contains a Retry-After header value. func RetryAfter(resp *http.Response) time.Duration { if resp == nil { return 0 } ra := resp.Header.Get(HeaderRetryAfter) if ra == "" { return 0 } // retry-after values are expressed in either number of // seconds or an HTTP-date indicating when to try again if retryAfter, _ := strconv.Atoi(ra); retryAfter > 0 { return time.Duration(retryAfter) * time.Second } else if t, err := time.Parse(time.RFC1123, ra); err == nil { return time.Until(t) } return 0 } // TypeOfT returns the type of the generic type param. func TypeOfT[T any]() reflect.Type { // you can't, at present, obtain the type of // a type parameter, so this is the trick return reflect.TypeOf((*T)(nil)).Elem() } // BytesSetter abstracts replacing a byte slice on some type. type BytesSetter interface { Set(b []byte) } // NewNopClosingBytesReader creates a new *NopClosingBytesReader for the specified slice. func NewNopClosingBytesReader(data []byte) *NopClosingBytesReader { return &NopClosingBytesReader{s: data} } // NopClosingBytesReader is an io.ReadSeekCloser around a byte slice. // It also provides direct access to the byte slice to avoid rereading. type NopClosingBytesReader struct { s []byte i int64 } // Bytes returns the underlying byte slice. func (r *NopClosingBytesReader) Bytes() []byte { return r.s } // Close implements the io.Closer interface. func (*NopClosingBytesReader) Close() error { return nil } // Read implements the io.Reader interface. func (r *NopClosingBytesReader) Read(b []byte) (n int, err error) { if r.i >= int64(len(r.s)) { return 0, io.EOF } n = copy(b, r.s[r.i:]) r.i += int64(n) return } // Set replaces the existing byte slice with the specified byte slice and resets the reader. func (r *NopClosingBytesReader) Set(b []byte) { r.s = b r.i = 0 } // Seek implements the io.Seeker interface. func (r *NopClosingBytesReader) Seek(offset int64, whence int) (int64, error) { var i int64 switch whence { case io.SeekStart: i = offset case io.SeekCurrent: i = r.i + offset case io.SeekEnd: i = int64(len(r.s)) + offset default: return 0, errors.New("nopClosingBytesReader: invalid whence") } if i < 0 { return 0, errors.New("nopClosingBytesReader: negative position") } r.i = i return i, nil } var _ BytesSetter = (*NopClosingBytesReader)(nil) // TransportFunc is a helper to use a first-class func to satisfy the Transporter interface. type TransportFunc func(*http.Request) (*http.Response, error) // Do implements the Transporter interface for the TransportFunc type. func (pf TransportFunc) Do(req *http.Request) (*http.Response, error) { return pf(req) } // ValidateModVer verifies that moduleVersion is a valid semver 2.0 string. func ValidateModVer(moduleVersion string) error { modVerRegx := regexp.MustCompile(`^v\d+\.\d+\.\d+(?:-[a-zA-Z0-9_.-]+)?$`) if !modVerRegx.MatchString(moduleVersion) { return fmt.Errorf("malformed moduleVersion param value %s", moduleVersion) } return nil } // ExtractPackageName returns "package" from "package.Client". // If clientName is malformed, an error is returned. func ExtractPackageName(clientName string) (string, error) { pkg, client, ok := strings.Cut(clientName, ".") if !ok { return "", fmt.Errorf("missing . in clientName %s", clientName) } else if pkg == "" || client == "" { return "", fmt.Errorf("malformed clientName %s", clientName) } return pkg, nil }