github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/internalutils/stream/stream.go (about)

     1  /*
     2  Copyright 2022 Gravitational, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package stream
    18  
    19  import (
    20  	"errors"
    21  	"io"
    22  
    23  	"github.com/gravitational/trace"
    24  )
    25  
    26  // Stream is a generic interface for streaming APIs. This package was built with the
    27  // intention of making it easier to write streaming resource getters, and may not be
    28  // be suitable for applications outside of that specific usecase. Streams may panic if
    29  // misused. See the Collect function for an example of the correct consumption pattern.
    30  //
    31  // NOTE: streams almost always perform worse than slices in go. unless you're dealing
    32  // with a resource that scales linearly with cluster size, you are probably better off
    33  // just working with slices.
    34  type Stream[T any] interface {
    35  	// Next attempts to advance the stream to the next item. If false is returned,
    36  	// then no more items are available. Next() and Item() must not be called after the
    37  	// first time Next() returns false.
    38  	Next() bool
    39  	// Item gets the current item. Invoking Item() is only safe if Next was previously
    40  	// invoked *and* returned true. Invoking Item() before invoking Next(), or after Next()
    41  	// returned false may cause panics or other unpredictable behavior. Whether or not the
    42  	// item returned is safe for access after the stream is advanced again is dependent
    43  	// on the implementation and should be documented (e.g. an I/O based stream might
    44  	// re-use an underlying buffer).
    45  	Item() T
    46  	// Done checks for any errors that occurred during streaming and informs the stream
    47  	// that we've finished consuming items from it. Invoking Next() or Item() after Done()
    48  	// has been called is not permitted. Done may trigger cleanup operations, but unlike Close()
    49  	// the error reported is specifically related to failures that occurred *during* streaming,
    50  	// meaning that if Done() returns an error, there is a high likelihood that the complete
    51  	// set of values was not observed. For this reason, Done() should always be checked explicitly
    52  	// rather than deferred as Close() might be.
    53  	Done() error
    54  }
    55  
    56  // streamFunc is a wrapper that converts a closure into a stream.
    57  type streamFunc[T any] struct {
    58  	fn        func() (T, error)
    59  	doneFuncs []func()
    60  	item      T
    61  	err       error
    62  }
    63  
    64  func (stream *streamFunc[T]) Next() bool {
    65  	stream.item, stream.err = stream.fn()
    66  	return stream.err == nil
    67  }
    68  
    69  func (stream *streamFunc[T]) Item() T {
    70  	return stream.item
    71  }
    72  
    73  func (stream *streamFunc[T]) Done() error {
    74  	for _, fn := range stream.doneFuncs {
    75  		fn()
    76  	}
    77  	if errors.Is(stream.err, io.EOF) {
    78  		return nil
    79  	}
    80  	return stream.err
    81  }
    82  
    83  // Func builds a stream from a closure. The supplied closure *must*
    84  // return io.EOF if no more items are available. Failure to return io.EOF
    85  // (or some other error) may cause infinite loops. Cleanup functions may
    86  // be optionally provided which will be run on close. If wrapping a
    87  // paginated API, consider using PageFunc instead.
    88  func Func[T any](fn func() (T, error), doneFuncs ...func()) Stream[T] {
    89  	return &streamFunc[T]{
    90  		fn:        fn,
    91  		doneFuncs: doneFuncs,
    92  	}
    93  }
    94  
    95  // Collect aggregates a stream into a slice. If an error is hit, the
    96  // items observed thus far are still returned, but they may not represent
    97  // the complete set.
    98  func Collect[T any](stream Stream[T]) ([]T, error) {
    99  	var c []T
   100  	for stream.Next() {
   101  		c = append(c, stream.Item())
   102  	}
   103  	return c, trace.Wrap(stream.Done())
   104  }
   105  
   106  // CollectPages aggregates a paginated stream into a slice. If an error
   107  // is hit, the pages observed thus far are still returned, but they may not
   108  // represent the complete set.
   109  func CollectPages[T any](stream Stream[[]T]) ([]T, error) {
   110  	var c []T
   111  	for stream.Next() {
   112  		c = append(c, stream.Item()...)
   113  	}
   114  	return c, trace.Wrap(stream.Done())
   115  }
   116  
   117  // filterMap is a stream that performs a FilterMap operation.
   118  type filterMap[A, B any] struct {
   119  	inner Stream[A]
   120  	fn    func(A) (B, bool)
   121  	item  B
   122  }
   123  
   124  func (stream *filterMap[A, B]) Next() bool {
   125  	for {
   126  		if !stream.inner.Next() {
   127  			return false
   128  		}
   129  		var ok bool
   130  		stream.item, ok = stream.fn(stream.inner.Item())
   131  		if !ok {
   132  			continue
   133  		}
   134  		return true
   135  	}
   136  }
   137  
   138  func (stream *filterMap[A, B]) Item() B {
   139  	return stream.item
   140  }
   141  
   142  func (stream *filterMap[A, B]) Done() error {
   143  	return stream.inner.Done()
   144  }
   145  
   146  // FilterMap maps a stream of type A into a stream of type B, filtering out
   147  // items when fn returns false.
   148  func FilterMap[A, B any](stream Stream[A], fn func(A) (B, bool)) Stream[B] {
   149  	return &filterMap[A, B]{
   150  		inner: stream,
   151  		fn:    fn,
   152  	}
   153  }
   154  
   155  // mapWhile is a stream that performs a MapWhile operation.
   156  type mapWhile[A, B any] struct {
   157  	inner Stream[A]
   158  	fn    func(A) (B, bool)
   159  	item  B
   160  }
   161  
   162  func (stream *mapWhile[A, B]) Next() bool {
   163  	if !stream.inner.Next() {
   164  		return false
   165  	}
   166  
   167  	var ok bool
   168  	stream.item, ok = stream.fn(stream.inner.Item())
   169  	return ok
   170  }
   171  
   172  func (stream *mapWhile[A, B]) Item() B {
   173  	return stream.item
   174  }
   175  
   176  func (stream *mapWhile[A, B]) Done() error {
   177  	return stream.inner.Done()
   178  }
   179  
   180  // MapWhile maps a stream of type A into a stream of type B, halting early
   181  // if fn returns false.
   182  func MapWhile[A, B any](stream Stream[A], fn func(A) (B, bool)) Stream[B] {
   183  	return &mapWhile[A, B]{
   184  		inner: stream,
   185  		fn:    fn,
   186  	}
   187  }
   188  
   189  // chain is a stream that performs a Chain operation.
   190  type chain[T any] struct {
   191  	streams []Stream[T]
   192  	err     error
   193  }
   194  
   195  func (stream *chain[T]) Next() bool {
   196  	for len(stream.streams) != 0 && stream.err == nil {
   197  		if stream.streams[0].Next() {
   198  			return true
   199  		}
   200  
   201  		stream.err = stream.streams[0].Done()
   202  		stream.streams[0] = nil
   203  		stream.streams = stream.streams[1:]
   204  	}
   205  
   206  	return false
   207  }
   208  
   209  func (stream *chain[T]) Item() T {
   210  	return stream.streams[0].Item()
   211  }
   212  
   213  func (stream *chain[T]) Done() error {
   214  	for _, s := range stream.streams {
   215  		s.Done()
   216  	}
   217  	stream.streams = nil
   218  
   219  	return stream.err
   220  }
   221  
   222  // Chain joins multiple streams in order, fully consuming one before moving to the next.
   223  func Chain[T any](streams ...Stream[T]) Stream[T] {
   224  	return &chain[T]{
   225  		streams: streams,
   226  	}
   227  }
   228  
   229  // empty is a stream that halts immediately
   230  type empty[T any] struct {
   231  	err error
   232  }
   233  
   234  func (stream empty[T]) Next() bool {
   235  	return false
   236  }
   237  
   238  func (stream empty[T]) Item() T {
   239  	panic("Item() called on empty/failed stream")
   240  }
   241  
   242  func (stream empty[T]) Done() error {
   243  	return stream.err
   244  }
   245  
   246  // Fail creates an empty stream that fails immediately with the supplied error.
   247  func Fail[T any](err error) Stream[T] {
   248  	return empty[T]{err}
   249  }
   250  
   251  // Empty creates an empty stream (equivalent to Fail(nil)).
   252  func Empty[T any]() Stream[T] {
   253  	return empty[T]{}
   254  }
   255  
   256  // once is a stream that yields a single item
   257  type once[T any] struct {
   258  	yielded bool
   259  	item    T
   260  }
   261  
   262  func (stream *once[T]) Next() bool {
   263  	if stream.yielded {
   264  		return false
   265  	}
   266  	stream.yielded = true
   267  	return true
   268  }
   269  
   270  func (stream *once[T]) Item() T {
   271  	return stream.item
   272  }
   273  
   274  func (stream *once[T]) Done() error {
   275  	return nil
   276  }
   277  
   278  // Once creates a stream that yields a single item.
   279  func Once[T any](item T) Stream[T] {
   280  	return &once[T]{
   281  		item: item,
   282  	}
   283  }
   284  
   285  // onceFunc is a stream that produces zero or one items based on
   286  // a lazily evaluated closure.
   287  type onceFunc[T any] struct {
   288  	fn   func() (T, error)
   289  	item T
   290  	err  error
   291  }
   292  
   293  func (stream *onceFunc[T]) Next() bool {
   294  	if stream.fn == nil {
   295  		return false
   296  	}
   297  
   298  	stream.item, stream.err = stream.fn()
   299  	stream.fn = nil
   300  	return stream.err == nil
   301  }
   302  
   303  func (stream *onceFunc[T]) Item() T {
   304  	return stream.item
   305  }
   306  
   307  func (stream *onceFunc[T]) Done() error {
   308  	if errors.Is(stream.err, io.EOF) {
   309  		return nil
   310  	}
   311  	return stream.err
   312  }
   313  
   314  // OnceFunc builds a stream from a closure that will yield exactly zero or one items. This stream
   315  // is the lazy equivalent of the Once/Fail/Empty combinators. A nil error value results
   316  // in a single-element stream. An error value of io.EOF results in an empty stream. All other error
   317  // values result in a failing stream.
   318  func OnceFunc[T any](fn func() (T, error)) Stream[T] {
   319  	return &onceFunc[T]{
   320  		fn: fn,
   321  	}
   322  }
   323  
   324  // Drain consumes a stream to completion.
   325  func Drain[T any](stream Stream[T]) error {
   326  	for stream.Next() {
   327  	}
   328  	return trace.Wrap(stream.Done())
   329  }
   330  
   331  // slice streams the elements of a slice
   332  type slice[T any] struct {
   333  	items []T
   334  	idx   int
   335  }
   336  
   337  func (s *slice[T]) Next() bool {
   338  	s.idx++
   339  	return len(s.items) > s.idx
   340  }
   341  
   342  func (s *slice[T]) Item() T {
   343  	return s.items[s.idx]
   344  }
   345  
   346  func (s *slice[T]) Done() error {
   347  	return nil
   348  }
   349  
   350  // Slice constructs a stream from a slice.
   351  func Slice[T any](items []T) Stream[T] {
   352  	return &slice[T]{
   353  		items: items,
   354  		idx:   -1,
   355  	}
   356  }
   357  
   358  type pageFunc[T any] struct {
   359  	inner streamFunc[[]T]
   360  	page  slice[T]
   361  }
   362  
   363  func (d *pageFunc[T]) Next() bool {
   364  	for {
   365  		if d.page.Next() {
   366  			return true
   367  		}
   368  		if !d.inner.Next() {
   369  			return false
   370  		}
   371  		d.page = slice[T]{
   372  			items: d.inner.Item(),
   373  			idx:   -1,
   374  		}
   375  	}
   376  }
   377  
   378  func (d *pageFunc[T]) Item() T {
   379  	return d.page.Item()
   380  }
   381  
   382  func (d *pageFunc[T]) Done() error {
   383  	return d.inner.Done()
   384  }
   385  
   386  // PageFunc is equivalent to Func except that it performs internal depagination. As with
   387  // Func, the supplied closure *must* return io.EOF if no more items are available. Failure
   388  // to return io.EOF (or some other error) may result in infinite loops.
   389  func PageFunc[T any](fn func() ([]T, error), doneFuncs ...func()) Stream[T] {
   390  	return &pageFunc[T]{
   391  		inner: streamFunc[[]T]{
   392  			fn:        fn,
   393  			doneFuncs: doneFuncs,
   394  		},
   395  	}
   396  }
   397  
   398  // Take takes the next n items from a stream. It returns a slice of the items
   399  // and the result of the last call to stream.Next().
   400  func Take[T any](stream Stream[T], n int) ([]T, bool) {
   401  	items := make([]T, 0, n)
   402  	for i := 0; i < n; i++ {
   403  		if !stream.Next() {
   404  			return items, false
   405  		}
   406  		items = append(items, stream.Item())
   407  	}
   408  	return items, true
   409  }
   410  
   411  type rateLimit[T any] struct {
   412  	inner   Stream[T]
   413  	wait    func() error
   414  	waitErr error
   415  }
   416  
   417  func (stream *rateLimit[T]) Next() bool {
   418  	stream.waitErr = stream.wait()
   419  	if stream.waitErr != nil {
   420  		return false
   421  	}
   422  
   423  	return stream.inner.Next()
   424  }
   425  
   426  func (stream *rateLimit[T]) Item() T {
   427  	return stream.inner.Item()
   428  }
   429  
   430  func (stream *rateLimit[T]) Done() error {
   431  	if err := stream.inner.Done(); err != nil {
   432  		return err
   433  	}
   434  
   435  	if errors.Is(stream.waitErr, io.EOF) {
   436  		return nil
   437  	}
   438  
   439  	return stream.waitErr
   440  }
   441  
   442  // RateLimit applies a rate-limiting function to a stream s.t. calls to Next() block on
   443  // the supplied function before calling the inner stream. If the function returns an
   444  // error, the inner stream is not polled and Next() returns false. The wait function may
   445  // return io.EOF to indicate a graceful/expected halting condition. Any other error value
   446  // is treated as unexpected and will be bubbled up via Done() unless an error from the
   447  // inner stream takes precedence.
   448  func RateLimit[T any](stream Stream[T], wait func() error) Stream[T] {
   449  	return &rateLimit[T]{
   450  		inner: stream,
   451  		wait:  wait,
   452  	}
   453  }
   454  
   455  // mergedStream is an adapter for merging two streams based on a less function, it will get the next items from both streams and yield the result of streamA if the comparison function returns true,
   456  // or the result of streamB if false.
   457  // The streams can be of different types, however, the resulting merged stream must be of one type.
   458  type mergedStream[T, U, V any] struct {
   459  	streamA Stream[T]
   460  	streamB Stream[U]
   461  	itemA   T
   462  	itemB   U
   463  	// hasItemA keeps track of whether we have an item available from streamA.
   464  	// We use this flag instead of just checking whether itemA is nil in order to ensure that this adapter also works with non-nullable types.
   465  	hasItemA bool
   466  	// hasItemB keeps track of whether we have an item available from streamB.
   467  	hasItemB bool
   468  	// less is the comparison function used to compare the items from both lists. If true, the merged stream will yield the item from streamA, if false, it will yield the item from streamB.
   469  	less func(a T, b U) bool
   470  	// convert is the function that is used to convert an item from the streamB type into the V type which is the type of the merged stream.
   471  	// If the streams are of the same type, this can simply be a function that returns the item as-is.
   472  	// convertA converts an item from streamA into the V type.
   473  	convertA func(item T) V
   474  	// convertB converts an item from streamB into the V type.
   475  	convertB func(item U) V
   476  }
   477  
   478  // Next attempts to advance each stream to the next item. If false is returned, then no more items are available.
   479  func (ms *mergedStream[T, U, V]) Next() bool {
   480  	// Attempt to advance to the next item in streamA, if we don't already have an item from it.
   481  	if !ms.hasItemA && ms.streamA.Next() {
   482  		ms.itemA = ms.streamA.Item()
   483  		ms.hasItemA = true
   484  	}
   485  	// Attempt to advance to the next item in streamB, if we don't already  have an item from it.
   486  	if !ms.hasItemB && ms.streamB.Next() {
   487  		ms.itemB = ms.streamB.Item()
   488  		ms.hasItemB = true
   489  	}
   490  
   491  	// Return true if either stream has an item available.
   492  	return ms.hasItemA || ms.hasItemB
   493  }
   494  
   495  // Item yields the current item.
   496  // If only an item from one stream is available, then it will yield that item.
   497  // If the items from both streams are available, then the less function will be used to decide which item to yield.
   498  // After yielding an item, it will also reset the availability flag of the yielded item's stream so that subsequent calls know to fetch a new item.
   499  func (ms *mergedStream[T, U, V]) Item() V {
   500  	// If both streams have items available, use the less function to determine which to yield.
   501  	if ms.hasItemA && ms.hasItemB {
   502  		if ms.less(ms.itemA, ms.itemB) {
   503  			// Reset the hasItemA flag since it has been consumed.
   504  			ms.hasItemA = false
   505  			return ms.convertA(ms.itemA)
   506  		}
   507  		// Reset the hasItemB flag since it has been consumed.
   508  		ms.hasItemB = false
   509  		return ms.convertB(ms.itemB)
   510  
   511  	}
   512  
   513  	// If only streamA has an item available, yield it.
   514  	if ms.hasItemA {
   515  		ms.hasItemA = false
   516  		return ms.convertA(ms.itemA)
   517  	}
   518  
   519  	// If only streamB has an item available, yield it.
   520  	if ms.hasItemB {
   521  		ms.hasItemB = false
   522  		return ms.convertB(ms.itemB)
   523  	}
   524  
   525  	// If neither stream has an available item, then this function should not have been called at all and there is a logic error.
   526  	panic("Item() was called but neither stream has an item, this is a bug")
   527  }
   528  
   529  // Done closes both streams.
   530  func (ms *mergedStream[T, U, V]) Done() error {
   531  	errA := ms.streamA.Done()
   532  	errB := ms.streamB.Done()
   533  
   534  	if errA != nil && errB != nil {
   535  		return trace.NewAggregate(errA, errB)
   536  	}
   537  	if errA != nil {
   538  		return trace.Wrap(errA, "failed to close the first stream")
   539  	}
   540  	if errB != nil {
   541  		return trace.Wrap(errB, "failed to close the second stream")
   542  	}
   543  
   544  	return nil
   545  }
   546  
   547  // MergeStreams merges two streams and returns a single stream which uses the provided less function to determine which item to yield.
   548  func MergeStreams[T any, U, V any](
   549  	streamA Stream[T],
   550  	streamB Stream[U],
   551  	less func(a T, b U) bool,
   552  	convertA func(item T) V,
   553  	convertB func(item U) V,
   554  ) Stream[V] {
   555  	return &mergedStream[T, U, V]{
   556  		streamA:  streamA,
   557  		streamB:  streamB,
   558  		less:     less,
   559  		convertA: convertA,
   560  		convertB: convertB,
   561  	}
   562  }