Skip to content

Commit e087538

Browse files
committed
feat(rx): add first/first-or-default operators (#176)
1 parent b302d80 commit e087538

File tree

3 files changed

+221
-0
lines changed

3 files changed

+221
-0
lines changed

rx/observable-operator-first_test.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package rx_test
2+
3+
import (
4+
"context"
5+
6+
"github.com/fortytw2/leaktest"
7+
. "github.com/onsi/ginkgo/v2" //nolint:revive // ginkgo ok
8+
"github.com/snivilised/lorax/rx"
9+
)
10+
11+
var _ = Describe("Observable operator", func() {
12+
Context("First", func() {
13+
When("not empty", func() {
14+
It("🧪 should: return first item", func() {
15+
// rxgo: Test_Observable_First_NotEmpty
16+
defer leaktest.Check(GinkgoT())()
17+
18+
ctx, cancel := context.WithCancel(context.Background())
19+
defer cancel()
20+
21+
obs := testObservable[int](ctx, 1, 2, 3).First()
22+
rx.Assert(ctx, obs, rx.HasItem[int]{
23+
Expected: 1,
24+
})
25+
})
26+
})
27+
28+
When("empty", func() {
29+
It("🧪 should: return nothing", func() {
30+
// rxgo: Test_Observable_First_Empty
31+
defer leaktest.Check(GinkgoT())()
32+
33+
ctx, cancel := context.WithCancel(context.Background())
34+
defer cancel()
35+
36+
obs := rx.Empty[int]().First()
37+
rx.Assert(ctx, obs, rx.IsEmpty[int]{})
38+
})
39+
})
40+
41+
Context("Parallel", func() {
42+
When("not empty", func() {
43+
It("🧪 should: return first item", func() {
44+
// rxgo: Test_Observable_First_Parallel_NotEmpty
45+
defer leaktest.Check(GinkgoT())()
46+
47+
ctx, cancel := context.WithCancel(context.Background())
48+
defer cancel()
49+
50+
obs := testObservable[int](ctx, 1, 2, 3).First(rx.WithCPUPool[int]())
51+
rx.Assert(ctx, obs, rx.HasItem[int]{
52+
Expected: 1,
53+
})
54+
})
55+
})
56+
57+
When("empty", func() {
58+
It("🧪 should: return nothing", func() {
59+
// rxgo: Test_Observable_First_Parallel_Empty
60+
defer leaktest.Check(GinkgoT())()
61+
62+
ctx, cancel := context.WithCancel(context.Background())
63+
defer cancel()
64+
65+
obs := rx.Empty[int]().First(rx.WithCPUPool[int]())
66+
rx.Assert(ctx, obs, rx.IsEmpty[int]{})
67+
})
68+
})
69+
})
70+
})
71+
72+
Context("FirstOrDefault", func() {
73+
When("not empty", func() {
74+
It("🧪 should: return first item", func() {
75+
// rxgo: Test_Observable_First_NotEmpty
76+
defer leaktest.Check(GinkgoT())()
77+
78+
ctx, cancel := context.WithCancel(context.Background())
79+
defer cancel()
80+
81+
obs := testObservable[int](ctx, 1, 2, 3).FirstOrDefault(10)
82+
rx.Assert(ctx, obs, rx.HasItem[int]{
83+
Expected: 1,
84+
})
85+
})
86+
})
87+
88+
When("empty", func() {
89+
It("🧪 should: return default", func() {
90+
// rxgo: Test_Observable_First_Empty
91+
defer leaktest.Check(GinkgoT())()
92+
93+
ctx, cancel := context.WithCancel(context.Background())
94+
defer cancel()
95+
96+
obs := rx.Empty[int]().FirstOrDefault(10)
97+
rx.Assert(ctx, obs, rx.HasItem[int]{
98+
Expected: 10,
99+
})
100+
})
101+
})
102+
103+
Context("Parallel", func() {
104+
When("not empty", func() {
105+
It("🧪 should: return first item", func() {
106+
// rxgo: Test_Observable_First_Parallel_NotEmpty
107+
defer leaktest.Check(GinkgoT())()
108+
109+
ctx, cancel := context.WithCancel(context.Background())
110+
defer cancel()
111+
112+
obs := testObservable[int](ctx, 1, 2, 3).FirstOrDefault(10, rx.WithCPUPool[int]())
113+
rx.Assert(ctx, obs, rx.HasItem[int]{
114+
Expected: 1,
115+
})
116+
})
117+
})
118+
119+
When("empty", func() {
120+
It("🧪 should: return default ", func() {
121+
// rxgo: Test_Observable_First_Parallel_Empty
122+
defer leaktest.Check(GinkgoT())()
123+
124+
ctx, cancel := context.WithCancel(context.Background())
125+
defer cancel()
126+
127+
obs := rx.Empty[int]().FirstOrDefault(10, rx.WithCPUPool[int]())
128+
rx.Assert(ctx, obs, rx.HasItem[int]{
129+
Expected: 10,
130+
})
131+
})
132+
})
133+
})
134+
})
135+
})

rx/observable-operator.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,90 @@ func (op *findOperator[T]) gatherNext(_ context.Context, _ Item[T],
746746
_ chan<- Item[T], _ operatorOptions[T]) {
747747
}
748748

749+
// First returns new Observable which emit only first item.
750+
// Cannot be run in parallel.
751+
func (o *ObservableImpl[T]) First(opts ...Option[T]) OptionalSingle[T] {
752+
const (
753+
forceSeq = true
754+
bypassGather = false
755+
)
756+
757+
return optionalSingle(o.parent, o, func() operator[T] {
758+
return &firstOperator[T]{}
759+
}, forceSeq, bypassGather, opts...)
760+
}
761+
762+
type firstOperator[T any] struct{}
763+
764+
func (op *firstOperator[T]) next(ctx context.Context, item Item[T],
765+
dst chan<- Item[T], operatorOptions operatorOptions[T],
766+
) {
767+
item.SendContext(ctx, dst)
768+
operatorOptions.stop()
769+
}
770+
771+
func (op *firstOperator[T]) err(ctx context.Context, item Item[T],
772+
dst chan<- Item[T], operatorOptions operatorOptions[T],
773+
) {
774+
defaultErrorFuncOperator(ctx, item, dst, operatorOptions)
775+
}
776+
777+
func (op *firstOperator[T]) end(_ context.Context, _ chan<- Item[T]) {
778+
}
779+
780+
func (op *firstOperator[T]) gatherNext(_ context.Context, _ Item[T],
781+
_ chan<- Item[T], _ operatorOptions[T],
782+
) {
783+
}
784+
785+
// FirstOrDefault returns new Observable which emit only first item.
786+
// If the observable fails to emit any items, it emits a default value.
787+
// Cannot be run in parallel.
788+
func (o *ObservableImpl[T]) FirstOrDefault(defaultValue T, opts ...Option[T]) Single[T] {
789+
const (
790+
forceSeq = true
791+
bypassGather = false
792+
)
793+
794+
return single(o.parent, o, func() operator[T] {
795+
return &firstOrDefaultOperator[T]{
796+
defaultValue: defaultValue,
797+
}
798+
}, forceSeq, bypassGather, opts...)
799+
}
800+
801+
type firstOrDefaultOperator[T any] struct {
802+
defaultValue T
803+
sent bool
804+
}
805+
806+
func (op *firstOrDefaultOperator[T]) next(ctx context.Context, item Item[T],
807+
dst chan<- Item[T], operatorOptions operatorOptions[T],
808+
) {
809+
item.SendContext(ctx, dst)
810+
811+
op.sent = true
812+
813+
operatorOptions.stop()
814+
}
815+
816+
func (op *firstOrDefaultOperator[T]) err(ctx context.Context, item Item[T],
817+
dst chan<- Item[T], operatorOptions operatorOptions[T],
818+
) {
819+
defaultErrorFuncOperator(ctx, item, dst, operatorOptions)
820+
}
821+
822+
func (op *firstOrDefaultOperator[T]) end(ctx context.Context, dst chan<- Item[T]) {
823+
if !op.sent {
824+
Of(op.defaultValue).SendContext(ctx, dst)
825+
}
826+
}
827+
828+
func (op *firstOrDefaultOperator[T]) gatherNext(_ context.Context, _ Item[T],
829+
_ chan<- Item[T], _ operatorOptions[T],
830+
) {
831+
}
832+
749833
// !!!
750834

751835
// Max determines and emits the maximum-valued item emitted by an Observable according to a comparator.

rx/observable.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ type Observable[T any] interface {
2828
Errors(opts ...Option[T]) []error
2929
Filter(apply Predicate[T], opts ...Option[T]) Observable[T]
3030
Find(find Predicate[T], opts ...Option[T]) OptionalSingle[T]
31+
First(opts ...Option[T]) OptionalSingle[T]
32+
FirstOrDefault(defaultValue T, opts ...Option[T]) Single[T]
3133
Max(comparator Comparator[T], initLimit InitLimit[T], opts ...Option[T]) OptionalSingle[T]
3234
Map(apply Func[T], opts ...Option[T]) Observable[T]
3335
Min(comparator Comparator[T], initLimit InitLimit[T], opts ...Option[T]) OptionalSingle[T]

0 commit comments

Comments
 (0)