...

Source file src/github.com/launchdarkly/go-server-sdk/v6/internal/datasource/helpers.go

Documentation: github.com/launchdarkly/go-server-sdk/v6/internal/datasource

     1  package datasource
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  
     7  	"github.com/launchdarkly/go-sdk-common/v3/ldlog"
     8  	"github.com/launchdarkly/go-server-sdk/v6/internal/datakinds"
     9  	st "github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoretypes"
    10  
    11  	"github.com/launchdarkly/go-jsonstream/v3/jreader"
    12  )
    13  
    14  type httpStatusError struct {
    15  	Message string
    16  	Code    int
    17  }
    18  
    19  func (e httpStatusError) Error() string {
    20  	return e.Message
    21  }
    22  
    23  // Tests whether an HTTP error status represents a condition that might resolve on its own if we retry,
    24  // or at least should not make us permanently stop sending requests.
    25  func isHTTPErrorRecoverable(statusCode int) bool {
    26  	if statusCode >= 400 && statusCode < 500 {
    27  		switch statusCode {
    28  		case 400: // bad request
    29  			return true
    30  		case 408: // request timeout
    31  			return true
    32  		case 429: // too many requests
    33  			return true
    34  		default:
    35  			return false // all other 4xx errors are unrecoverable
    36  		}
    37  	}
    38  	return true
    39  }
    40  
    41  func httpErrorDescription(statusCode int) string {
    42  	message := ""
    43  	if statusCode == 401 || statusCode == 403 {
    44  		message = " (invalid SDK key)"
    45  	}
    46  	return fmt.Sprintf("HTTP error %d%s", statusCode, message)
    47  }
    48  
    49  // Logs an HTTP error or network error at the appropriate level and determines whether it is recoverable
    50  // (as defined by isHTTPErrorRecoverable).
    51  func checkIfErrorIsRecoverableAndLog(
    52  	loggers ldlog.Loggers,
    53  	errorDesc, errorContext string,
    54  	statusCode int,
    55  	recoverableMessage string,
    56  ) bool {
    57  	if statusCode > 0 && !isHTTPErrorRecoverable(statusCode) {
    58  		loggers.Errorf("Error %s (giving up permanently): %s", errorContext, errorDesc)
    59  		return false
    60  	}
    61  	loggers.Warnf("Error %s (%s): %s", errorContext, recoverableMessage, errorDesc)
    62  	return true
    63  }
    64  
    65  func checkForHTTPError(statusCode int, url string) error {
    66  	if statusCode == http.StatusUnauthorized {
    67  		return httpStatusError{
    68  			Message: fmt.Sprintf("Invalid SDK key when accessing URL: %s. Verify that your SDK key is correct.", url),
    69  			Code:    statusCode}
    70  	}
    71  
    72  	if statusCode == http.StatusNotFound {
    73  		return httpStatusError{
    74  			Message: fmt.Sprintf("Resource not found when accessing URL: %s. Verify that this resource exists.", url),
    75  			Code:    statusCode}
    76  	}
    77  
    78  	if statusCode/100 != 2 {
    79  		return httpStatusError{
    80  			Message: fmt.Sprintf("Unexpected response code: %d when accessing URL: %s", statusCode, url),
    81  			Code:    statusCode}
    82  	}
    83  	return nil
    84  }
    85  
    86  // This method parses a JSON data structure representing a full set of SDK data. For example:
    87  //
    88  //	{
    89  //	  "flags": {
    90  //	    "flag1": { "key": "flag1", "version": 1, ...etc. },
    91  //	    "flag2": { "key": "flag2", "version": 1, ...etc. },
    92  //	  },
    93  //	  "segments": {
    94  //	    "segment1": { "key", "segment1", "version": 1, ...etc. }
    95  //	  }
    96  //	}
    97  //
    98  // Even though this is map-like, we don't return the data as a map, because the SDK does not need to
    99  // manipulate it as a map. Our data store API instead expects a list of Collections, each of which has
   100  // a list of data items, so that's what we build here.
   101  //
   102  // This representation makes up the entirety of a polling response for PollingDataSource, and is a
   103  // subset of the stream data for StreamingDataSource.
   104  func parseAllStoreDataFromJSONReader(r *jreader.Reader) []st.Collection {
   105  	var ret []st.Collection
   106  	for dataObj := r.Object(); dataObj.Next(); {
   107  		var dataKind datakinds.DataKindInternal
   108  		switch string(dataObj.Name()) {
   109  		case "flags":
   110  			dataKind = datakinds.Features
   111  		case "segments":
   112  			dataKind = datakinds.Segments
   113  		default: // unrecognized category, skip it
   114  			continue
   115  		}
   116  		coll := st.Collection{Kind: dataKind}
   117  		for keysToItemsObj := r.Object(); keysToItemsObj.Next(); {
   118  			key := string(keysToItemsObj.Name())
   119  			item, err := dataKind.DeserializeFromJSONReader(r)
   120  			if err == nil {
   121  				coll.Items = append(coll.Items, st.KeyedItemDescriptor{Key: key, Item: item})
   122  			}
   123  		}
   124  		ret = append(ret, coll)
   125  	}
   126  	return ret
   127  }
   128  

View as plain text