Coverage Report

Created: 2026-11-13 23:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/build/source/nativelink-store/src/redis_utils/ft_cursor_read.rs
Line
Count
Source
1
// Copyright 2025 Trace Machina, Inc. All rights reserved.
2
//
3
// Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//    See LICENSE file for details
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
use redis::aio::ConnectionLike;
16
use redis::{ErrorKind, RedisError, Value};
17
18
use super::ft_aggregate::{resp2_data_parse, resp3_data_parse};
19
use crate::redis_utils::aggregate_types::RedisCursorData;
20
21
2
pub(crate) async fn ft_cursor_read<C>(
22
2
    connection_manager: &mut C,
23
2
    index: String,
24
2
    cursor_id: u64,
25
2
) -> Result<RedisCursorData, RedisError>
26
2
where
27
2
    C: ConnectionLike + Send,
28
2
{
29
2
    let mut cmd = redis::cmd("ft.cursor");
30
2
    let ft_cursor_cmd = cmd.arg("read").arg(index).cursor_arg(cursor_id);
31
2
    let data = ft_cursor_cmd
32
2
        .to_owned()
33
2
        .query_async::<Value>(connection_manager)
34
2
        .await
?0
;
35
2
    let Value::Array(value) = data else {
36
0
        return Err(RedisError::from((ErrorKind::Parse, "Expected array")));
37
    };
38
2
    if value.len() < 2 {
39
0
        return Err(RedisError::from((
40
0
            ErrorKind::Parse,
41
0
            "Expected at least 2 elements",
42
0
        )));
43
2
    }
44
2
    let mut value = value.into_iter();
45
    // The connection speaks RESP3, so a page past the first arrives as a
46
    // map, the same shape the aggregate itself returns; RESP2 gives the
47
    // bare array. Both parse the way the first page does, or every listing
48
    // longer than one page failed here on its second page.
49
2
    let mut output = RedisCursorData::default();
50
2
    match value.next().unwrap() {
51
1
        Value::Array(data_ary) => resp2_data_parse(&mut output, &data_ary)
?0
,
52
1
        Value::Map(data_map) => resp3_data_parse(&mut output, &data_map)
?0
,
53
0
        other => {
54
0
            return Err(RedisError::from((
55
0
                ErrorKind::Parse,
56
0
                "Expected array or map for cursor results",
57
0
                format!("{other:?}"),
58
0
            )));
59
        }
60
    }
61
2
    let Value::Int(new_cursor_id) = value.next().unwrap() else {
62
0
        return Err(RedisError::from((
63
0
            ErrorKind::Parse,
64
0
            "Expected cursor id as second element",
65
0
        )));
66
    };
67
2
    output.cursor = new_cursor_id as u64;
68
2
    Ok(output)
69
2
}