...

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

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

     1  package datasource
     2  
     3  import (
     4  	"errors"
     5  	"strings"
     6  
     7  	"github.com/launchdarkly/go-server-sdk/v6/internal/datakinds"
     8  	"github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoretypes"
     9  
    10  	"github.com/launchdarkly/go-jsonstream/v3/jreader"
    11  )
    12  
    13  var (
    14  	putDataRequiredProperties    = []string{"data"}            //nolint:gochecknoglobals
    15  	patchDataRequiredProperties  = []string{"path", "data"}    //nolint:gochecknoglobals
    16  	deleteDataRequiredProperties = []string{"path", "version"} //nolint:gochecknoglobals
    17  )
    18  
    19  // This is the logical representation of the data in the "put" event. In the JSON representation,
    20  // the "data" property is actually a map of maps, but the schema we use internally is a list of
    21  // lists instead.
    22  //
    23  // The "path" property is normally always "/"; the LD streaming service sends this property, but
    24  // some versions of Relay do not, so we do not require it.
    25  //
    26  // Example JSON representation:
    27  //
    28  //	{
    29  //	  "path": "/",
    30  //	  "data": {
    31  //	    "flags": {
    32  //	      "flag1": { "key": "flag1", "version": 1, ...etc. },
    33  //	      "flag2": { "key": "flag2", "version": 1, ...etc. },
    34  //	    },
    35  //	    "segments": {
    36  //	      "segment1": { "key", "segment1", "version": 1, ...etc. }
    37  //	    }
    38  //	  }
    39  //	}
    40  type putData struct {
    41  	Path string // we don't currently do anything with this
    42  	Data []ldstoretypes.Collection
    43  }
    44  
    45  // This is the logical representation of the data in the "patch" event. In the JSON representation,
    46  // there is a "path" property in the format "/flags/key" or "/segments/key", which we convert into
    47  // Kind and Key when we parse it. The "data" property is the JSON representation of the flag or
    48  // segment, which we deserialize into an ItemDescriptor.
    49  //
    50  // Example JSON representation:
    51  //
    52  //	{
    53  //	  "path": "/flags/flagkey",
    54  //	  "data": {
    55  //	    "key": "flagkey",
    56  //	    "version": 2, ...etc.
    57  //	  }
    58  //	}
    59  type patchData struct {
    60  	Kind ldstoretypes.DataKind
    61  	Key  string
    62  	Data ldstoretypes.ItemDescriptor
    63  }
    64  
    65  // This is the logical representation of the data in the "delete" event. In the JSON representation,
    66  // there is a "path" property in the format "/flags/key" or "/segments/key", which we convert into
    67  // Kind and Key when we parse it.
    68  //
    69  // Example JSON representation:
    70  //
    71  //	{
    72  //	  "path": "/flags/flagkey",
    73  //	  "version": 3
    74  //	}
    75  type deleteData struct {
    76  	Kind    ldstoretypes.DataKind
    77  	Key     string
    78  	Version int
    79  }
    80  
    81  func parsePutData(data []byte) (putData, error) {
    82  	var ret putData
    83  	r := jreader.NewReader(data)
    84  	for obj := r.Object().WithRequiredProperties(putDataRequiredProperties); obj.Next(); {
    85  		switch string(obj.Name()) {
    86  		case "path": //nolint:goconst // linter wants us to define constants, but that makes code like this less clear
    87  			ret.Path = r.String()
    88  		case "data": //nolint:goconst
    89  			ret.Data = parseAllStoreDataFromJSONReader(&r)
    90  		}
    91  	}
    92  	return ret, r.Error()
    93  }
    94  
    95  func parsePatchData(data []byte) (patchData, error) {
    96  	var ret patchData
    97  	r := jreader.NewReader(data)
    98  	var kind datakinds.DataKindInternal
    99  	var key string
   100  	parseItem := func() (patchData, error) {
   101  		item, err := kind.DeserializeFromJSONReader(&r)
   102  		if err != nil {
   103  			return patchData{}, err
   104  		}
   105  		ret.Data = item
   106  		return ret, nil
   107  	}
   108  	for obj := r.Object().WithRequiredProperties(patchDataRequiredProperties); obj.Next(); {
   109  		switch string(obj.Name()) {
   110  		case "path":
   111  			path := r.String()
   112  			kind, key = parsePath(path)
   113  			ret.Kind, ret.Key = kind, key
   114  			if kind == nil {
   115  				// An unrecognized path isn't considered an error; we'll just return a nil kind,
   116  				// indicating that we should ignore this event.
   117  				return ret, nil
   118  			}
   119  		case "data":
   120  			if kind != nil {
   121  				// If kind is nil, it means we happened to read the "data" property before the
   122  				// "path" property, so we don't yet know what kind of data model object this is,
   123  				// so we can't parse it yet and we'll have to do a second pass.
   124  				return parseItem()
   125  			}
   126  		}
   127  	}
   128  	if err := r.Error(); err != nil {
   129  		return patchData{}, err
   130  	}
   131  	// If we got here, it means we couldn't parse the data model object yet because we saw the
   132  	// "data" property first. But we definitely saw both properties (otherwise we would've got
   133  	// an error due to using WithRequiredProperties) so kind is now non-nil.
   134  	r = jreader.NewReader(data)
   135  	for obj := r.Object(); obj.Next(); {
   136  		if string(obj.Name()) == "data" {
   137  			return parseItem()
   138  		}
   139  	}
   140  	if r.Error() != nil {
   141  		return patchData{}, r.Error()
   142  	}
   143  	return patchData{}, errors.New("patch event had no data property")
   144  }
   145  
   146  func parseDeleteData(data []byte) (deleteData, error) {
   147  	var ret deleteData
   148  	r := jreader.NewReader(data)
   149  	for obj := r.Object().WithRequiredProperties(deleteDataRequiredProperties); obj.Next(); {
   150  		switch string(obj.Name()) {
   151  		case "path":
   152  			path := r.String()
   153  			ret.Kind, ret.Key = parsePath(path)
   154  			if ret.Kind == nil {
   155  				// An unrecognized path isn't considered an error; we'll just return a nil kind,
   156  				// indicating that we should ignore this event.
   157  				return ret, nil
   158  			}
   159  		case "version":
   160  			ret.Version = r.Int()
   161  		}
   162  	}
   163  	if r.Error() != nil {
   164  		return deleteData{}, r.Error()
   165  	}
   166  	return ret, nil
   167  }
   168  
   169  func parsePath(path string) (datakinds.DataKindInternal, string) {
   170  	switch {
   171  	case strings.HasPrefix(path, "/segments/"):
   172  		return datakinds.Segments, strings.TrimPrefix(path, "/segments/")
   173  	case strings.HasPrefix(path, "/flags/"):
   174  		return datakinds.Features, strings.TrimPrefix(path, "/flags/")
   175  	default:
   176  		return nil, ""
   177  	}
   178  }
   179  

View as plain text