1+ use serde:: Serialize ;
2+
3+ /// ApiPagedResponse indicates that this response of data array slice is part of a sorted list of items.
4+ /// Items are part of a larger sorted list and the slice indicates a window within the large sorted list.
5+ /// Each ApiPagedResponse holds the data array and the "token" for next part of the slice.
6+ /// The next token should be passed via query 'next' and only used when getting the next slice.
7+ /// Hence the first request, the next token is always empty and not provided.
8+ ///
9+ /// With ascending sorted list and a limit of 3 items per slice will have the behaviour as such.
10+ ///
11+ /// SORTED : | [1] [2] [3] | [4] [5] [6] | [7] [8] [9] | [10]
12+ /// Query 1 : Data: [1] [2] [3], Next: 3, Operator: GT (>)
13+ /// Query 2 : Data: [4] [5] [6], Next: 6, Operator: GT (>)
14+ /// Query 3 : Data: [7] [8] [9], Next: 3, Operator: GT (>)
15+ /// Query 4 : Data: [10], Next: undefined
16+ ///
17+ /// This design is resilient also mutating sorted list, where pagination is not.
18+ ///
19+ /// SORTED : [2] [4] [6] [8] [10] [12] [14]
20+ /// Query 1 : Data: [2] [4] [6], Next: 6, Operator: GT (>)
21+ ///
22+ /// Being in a slice window, the larger sorted list can be mutated.
23+ /// You only need the next token to get the next slice.
24+ /// MUTATED : [2] [4] [7] [8] [9] [10] [12] [14]
25+ /// Query 2 : Data: [7] [8] [9], Next: 6, Operator: GT (>)
26+ ///
27+ /// Limitations of this requires your dat astructure to always be sorted in one direction and your sort
28+ /// indexes always fixed. Hence the moving down of the slice window, your operator will be greater than (GT).
29+ /// While moving up your operator will be less than (GT).
30+ ///
31+ /// ASC : | [1] [2] [3] | [4] [5] [6] | [7] [8] [9] |
32+ /// >3 >6 >9
33+ /// DESC : | [9] [8] [7] | [6] [5] [4] | [3] [2] [1] |
34+ /// <7 <4 <1
35+ /// For developer quality life it's unwise to allow inclusive operator, it just creates more overhead
36+ /// to understanding our services. No GTE or LTE, always GT and LE. Services must beclean and clear,
37+ /// when the usage narrative is clear and so will the use of ease. LIST query must be dead simple.
38+ /// Image travelling down the path, and getting a "next token" to get the next set of itmes to
39+ /// continue walking.
40+ ///
41+ /// Because the limit is not part of the slice window your query mechanism should support varying size windows.
42+ ///
43+ /// DATA: | [1] [2] [3] | [4] [5] [6] [7] | [8] [9] | ...
44+ /// | limit 3, >3 | limit 4, >7 | limit 2, >9
45+ /// For simplicity your API should not attempt to allow access to different sort indexes, be cognizant of
46+ /// how our APIs are consumed. If we create a GET /blocks operation to list blocks what would the correct indexes
47+ /// be 99% of the time?
48+ ///
49+ /// Answer: Blocks sorted by height in descending order, that's your sorted list and your slice window.
50+ /// : <- Latest | [100] [99] [98] [97] [...] | Oldest ->
51+ ///
52+ #[ derive( Debug , Serialize , PartialEq ) ]
53+ pub struct ApiPagedResponse < T > {
54+ data : Vec < T > ,
55+ page : ApiPage ,
56+ }
57+
58+ #[ derive( Debug , Serialize , PartialEq ) ]
59+ struct ApiPage {
60+ next : Option < String > ,
61+ }
62+
63+ impl < T > ApiPagedResponse < T > {
64+ pub fn new ( data : Vec < T > , next : Option < String > ) -> Self {
65+ Self { data, page : ApiPage { next } }
66+ }
67+
68+ pub fn next ( data : Vec < T > , next : Option < String > ) -> Self {
69+ Self :: new ( data, next)
70+ }
71+
72+ pub fn of ( data : Vec < T > , limit : usize , next_provider : impl Fn ( & T ) -> String ) -> Self {
73+ if data. len ( ) == limit && data. len ( ) > 0 && limit > 0 {
74+ let next = next_provider ( & data[ limit - 1 ] ) ;
75+ Self :: next ( data, Some ( next) )
76+ } else {
77+ Self :: next ( data, None )
78+ }
79+ }
80+
81+ pub fn empty ( ) -> Self {
82+ Self :: new ( Vec :: new ( ) , None )
83+ }
84+ }
85+
86+ #[ cfg( test) ]
87+ mod tests {
88+ use super :: { ApiPagedResponse , ApiPage } ;
89+
90+ struct Item {
91+ id : String ,
92+ sort : String ,
93+ }
94+
95+ #[ test]
96+ fn should_next_with_none ( ) {
97+ let items: Vec < Item > = vec ! [
98+ Item { id: "1" . into( ) , sort: "a" . into( ) } ,
99+ Item { id: "2" . into( ) , sort: "b" . into( ) } ,
100+ ] ;
101+
102+ let next = ApiPagedResponse :: next ( items, None ) . page . next ;
103+ assert_eq ! ( next, None ) ;
104+ }
105+
106+ #[ test]
107+ fn should_next_with_value ( ) {
108+ let items: Vec < Item > = vec ! [
109+ Item { id: "1" . into( ) , sort: "a" . into( ) } ,
110+ Item { id: "2" . into( ) , sort: "b" . into( ) } ,
111+ ] ;
112+
113+ let next = ApiPagedResponse :: next ( items, Some ( "b" . into ( ) ) ) . page . next ;
114+ assert_eq ! ( next, Some ( "b" . into( ) ) ) ;
115+ }
116+
117+ #[ test]
118+ fn should_of_with_limit_3 ( ) {
119+ let items: Vec < Item > = vec ! [
120+ Item { id: "1" . into( ) , sort: "a" . into( ) } ,
121+ Item { id: "2" . into( ) , sort: "b" . into( ) } ,
122+ Item { id: "3" . into( ) , sort: "c" . into( ) } ,
123+ ] ;
124+
125+ let next = ApiPagedResponse :: of ( items, 3 , |item| item. sort . to_owned ( ) ) . page . next ;
126+ assert_eq ! ( next, Some ( "c" . into( ) ) )
127+ }
128+
129+ #[ test]
130+ fn should_not_create_with_limit_3_while_size_2 ( ) {
131+ let items: Vec < Item > = vec ! [
132+ Item { id: "1" . into( ) , sort: "a" . into( ) } ,
133+ Item { id: "2" . into( ) , sort: "b" . into( ) } ,
134+ ] ;
135+
136+ let page = ApiPagedResponse :: of ( items, 3 , |item| item. sort . to_owned ( ) ) . page ;
137+ assert_eq ! ( page, ApiPage { next: None } )
138+ }
139+
140+ }
0 commit comments