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 }