diff --git a/guile/b2-pitr.scm b/guile/b2-pitr.scm index 56950d8..9cf5447 100644 --- a/guile/b2-pitr.scm +++ b/guile/b2-pitr.scm @@ -19,6 +19,33 @@ (web response) (web uri)) +(define-exception-type &api-error &external-error + make-api-error + api-error? + (endpoint api-error-endpoint) + (status-code api-error-status-code) + (body api-error-body)) + +(define (json-response? res) + (eq? 'application/json (car (response-content-type res)))) + +(define (text-response? res) + (string-prefix? "text/" + (symbol->string (car (response-content-type res))))) + +(define-syntax check-api-response + (syntax-rules () + ((check-api-response endpoint res body-port) + (check-api-response endpoint res body-port (<= 100 (response-code res) 399))) + ((check-api-response endpoint res body-port ok?) + (unless ok? + (let ((body (cond + ((json-response? res) (json->scm body-port)) + ((text-response? res) (get-string-all body-port)) + (else "")))) + (close-port body-port) + (raise-exception (make-api-error endpoint (response-code res) body))))))) + (define (encode-query alist) (fold (match-lambda* (((k . v) s) @@ -32,15 +59,6 @@ "" alist)) -(define (http-ok? res) - (<= 200 (response-code res) 299)) - -(define (json-response? res) - (eq? 'application/json (car (response-content-type res)))) - -(define (text-response? res) - (string-prefix? "text/" - (symbol->string (car (response-content-type res))))) ;; We have to override the default handling of the Authorization header as the ;; Backblaze API expects just a bare string. The b2_authorize_account endpoint @@ -53,11 +71,7 @@ (define auth-url "https://api.backblazeb2.com/b2api/v4/b2_authorize_account") (let* ((token (base64-encode (string->bytevector (string-append key-id ":" app-key) "utf-8"))) (res body (http-get auth-url #:headers `((authorization . ,(string-append "Basic " token))) #:streaming? #t))) - (unless (http-ok? res) - (raise-exception (make-exception - (make-external-error) - (make-exception-with-message "Error response from b2_authorize_account") - (make-exception-with-irritants res)))) + (check-api-response "/b2api/v4/b2_authorize_account" res body (and (= 200 (response-code res)) (json-response? res))) (json->scm body))) (define (assoc-ref* alist . keys) @@ -82,28 +96,16 @@ (define-method (account-id (cli )) (assoc-ref (b2-auth-info cli) "accountId")) -(define (check-api-response res body-port err-msg) - (unless (http-ok? res) - (let ((body (cond - ((json-response? res) (json->scm body-port)) - ((text-response? res) (get-string-all body-port)) - (else "Binary response body")))) - (close-port body-port) - (raise-exception (make-exception - (make-external-error) - (make-exception-with-message err-msg) - (make-exception-with-irritants (get-string-all body))))))) - -(define* (api-call cli method path #:key query-params payload raw?) +(define* (api-call cli method endpoint #:key query-params payload raw?) (let* ((url (if query-params - (string-append (api-url cli) path "?" (encode-query query-params)) - (string-append (api-url cli) path))) + (string-append (api-url cli) endpoint "?" (encode-query query-params)) + (string-append (api-url cli) endpoint))) (res body-port (method url #:body (and=> payload scm->json-string) #:headers `((authorization . ,(auth-token cli)) (content-type . (application/json (charset . "utf-8")))) #:streaming? #t))) - (check-api-response res body-port (string-append "Error from " path)) + (check-api-response endpoint res body-port) (if raw? body-port (let ((body (cond @@ -233,6 +235,7 @@ resore these empty files." (define (main args) (define cli (make #:key-id (getenv "BB_KEY_ID") #:app-key (getenv "BB_APP_KEY"))) + (define bucket-id (resolve-bucket-id cli "uk-org-1729-server-backups")) (define cutoff (* 1000 (time-second (date->time-utc (string->date "2025-06-01 00:00:00+0000" "~Y-~m-~d ~H:~M:~S~z")))))