use faucet_core::FaucetError;
use jsonpath_rust::JsonPath;
use serde_json::Value;
pub fn advance(
body: &Value,
next_link_path: &str,
next_link: &mut Option<String>,
) -> Result<bool, FaucetError> {
let results = body
.query(next_link_path)
.map_err(|e| FaucetError::JsonPath(format!("{e}")))?;
match results.first() {
None | Some(Value::Null) => {
*next_link = None;
Ok(false)
}
Some(Value::String(s)) => {
if s.is_empty() {
*next_link = None;
Ok(false)
} else {
*next_link = Some(s.clone());
Ok(true)
}
}
Some(_) => Err(FaucetError::JsonPath(format!(
"next-link path '{next_link_path}' resolved to a non-string value; the \
next-page link must be a URL string"
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn extracts_next_link_url() {
let body = json!({"results": [], "next_link": "https://api.example.com/workers?page=2"});
let mut next_link = None;
let has_next = advance(&body, "$.next_link", &mut next_link).unwrap();
assert!(has_next);
assert_eq!(
next_link,
Some("https://api.example.com/workers?page=2".into())
);
}
#[test]
fn stops_on_null() {
let body = json!({"results": [], "next_link": null});
let mut next_link = Some("stale".into());
let has_next = advance(&body, "$.next_link", &mut next_link).unwrap();
assert!(!has_next);
assert!(next_link.is_none());
}
#[test]
fn stops_when_field_absent() {
let body = json!({"results": []});
let mut next_link = None;
let has_next = advance(&body, "$.next_link", &mut next_link).unwrap();
assert!(!has_next);
assert!(next_link.is_none());
}
#[test]
fn stops_on_empty_string() {
let body = json!({"results": [], "next_link": ""});
let mut next_link = None;
let has_next = advance(&body, "$.next_link", &mut next_link).unwrap();
assert!(!has_next);
assert!(next_link.is_none());
}
#[test]
fn non_string_next_link_errors() {
let mut next_link = None;
assert!(advance(&json!({"next_link": 5}), "$.next_link", &mut next_link).is_err());
assert!(
advance(
&json!({"next_link": {"u": "x"}}),
"$.next_link",
&mut next_link
)
.is_err()
);
}
}