char/dns-ez
cloudflare dns management via plaintext zonefiles
git clone https://git.t4t.associates/char/dns-ez
c6d896d
main
1use std:: time:: Duration ; 2 3use anyhow::{ Context , Result , anyhow}; 4use serde:: Serialize ; 5use ureq:: SendBody ; 6use ureq:: http::{ Method , Request }; 7 8const ATTEMPTS : u32 =4 ; 9 10/// A JSON HTTP API that authenticates every request with the same headers. 11pub struct Api { 12agent : ureq:: Agent , 13base : &' static str , 14headers : Vec <( &' static str , String )>, 15} 16 17impl Api { 18pub fn new ( base : &' static str , headers : Vec <( &' static str , String )>) ->Self { 19let config = ureq:: Agent :: config_builder () 20. timeout_global ( Some ( Duration :: from_secs ( 30 ))) 21. http_status_as_error ( false ) 22. build (); 23Api { 24agent : ureq:: Agent :: new_with_config ( config), 25 base, 26 headers, 27} 28} 29 30/// Send a request, retrying transport errors, 429s and 5xxs with backoff. 31/// Returns the final status and body: every API words its errors 32/// differently, so judging them is left to the caller. 33pub fn call ( 34& self , 35method : Method , 36path : & str , 37body : Option < & impl Serialize >, 38) ->Result <( u16 , String )> { 39let mut last_err =String :: new (); 40for attemptin 0 ..ATTEMPTS { 41if attempt >0 { 42 std:: thread:: sleep ( Duration :: from_millis ( 500 <<( attempt -1 ))); 43} 44let mut request =Request :: builder () 45. method ( method. clone ()) 46. uri ( format! ( "{}{path}" , self . base )); 47for ( name, value) in & self . headers { 48 request = request. header ( * name, value); 49} 50let body =match body{ 51Some ( b) =>SendBody :: from_json ( b) ?, 52None =>SendBody :: none (), 53}; 54let mut response =match self . agent . run ( request. body ( body) ?) { 55Ok ( r) => r, 56Err ( e) =>{ 57 last_err =format! ( "{method} {path}: {e}" ); 58continue ; 59} 60}; 61let status = response. status (). as_u16 (); 62if ( status ==429 || status >=500 ) && attempt +1 <ATTEMPTS { 63 last_err =format! ( "{method} {path}: HTTP {status}" ); 64continue ; 65} 66let text = response 67. body_mut () 68. read_to_string () 69. with_context ( ||format! ( "{method} {path}: unreadable response" )) ?; 70return Ok (( status, text)); 71} 72Err ( anyhow! ( last_err)) 73} 74}