okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
d9c1a13
main
1package daemon 2 3import ( 4"encoding/json" 5"errors" 6"fmt" 7"net/http" 8) 9 10// UpgradeRequiredError identifies a feature missing from the running daemon. 11type UpgradeRequiredError struct { 12Feature string 13} 14 15func (e * UpgradeRequiredError )Error ()string { 16return "the running copy of Albedo needs an update " + e .Feature + "; restarting will interrupt work in all sessions and clear their Python variables; when you are ready, run albedo daemon --stop, then launch your updated copy of Albedo" 17} 18 19// APIError retains the HTTP status and daemon error code independently of copy. 20type APIError struct { 21Cause error 22Code string 23Message string 24StatusCode int 25Decision json.RawMessage 26} 27 28func (e * APIError )Error ()string { 29if e .Message != "" { 30return e .Message 31 } 32if e .Cause != nil { 33return fmt .Sprintf ("HTTP %d: %v" ,e .StatusCode ,e .Cause ) 34 } 35if status := http .StatusText (e .StatusCode );status != "" { 36return fmt .Sprintf ("HTTP %d: %s" ,e .StatusCode ,status ) 37 } 38return fmt .Sprintf ("HTTP %d" ,e .StatusCode ) 39} 40 41func (e * APIError )Unwrap ()error {return e .Cause } 42 43// DaemonRestarting reports whether the daemon refused because it is going 44// away or not serving yet, so the one that serves next may answer. 45func (e * APIError )DaemonRestarting ()bool { 46return e .Code == "daemon_stopping" || e .Code == "daemon_unavailable" 47} 48 49func decodeAPIError (status int ,body []byte )error { 50var data struct { 51Code string `json:"code"` 52Detail string `json:"detail"` 53Title string `json:"title"` 54Decision json.RawMessage `json:"decision"` 55 } 56apiErr := & APIError {StatusCode :status } 57if err := json .Unmarshal (body ,& data );err != nil { 58return apiErr 59 } 60apiErr .Code ,apiErr .Message ,apiErr .Decision = data .Code ,data .Detail ,data .Decision 61if apiErr .Message == "" { 62apiErr .Message = data .Title 63 } 64return apiErr 65} 66 67// UncertainOutcomeError means the daemon may have applied a mutation. Callers 68// must reconcile its result before asking the user to submit it again. 69type UncertainOutcomeError struct { 70Cause error 71Operation string 72Handle * OperationHandle 73} 74 75func (e * UncertainOutcomeError )Error ()string { 76if e .Handle != nil { 77return e .Operation + " admission is uncertain; operation " + e .Handle .ID ()+ " can be queried" 78 } 79return e .Operation + " may have been accepted; check its result before trying again" 80} 81 82func (e * UncertainOutcomeError )Unwrap ()error {return e .Cause } 83 84// StreamFailureKind tells subscribers whether reconnecting can make progress. 85type StreamFailureKind uint8 86 87const ( 88StreamTransient StreamFailureKind = iota 89StreamTerminal 90StreamProtocol 91) 92 93// StreamError preserves the cause while defining subscription recovery. 94type StreamError struct { 95Kind StreamFailureKind 96Cause error 97} 98 99func (e * StreamError )Error ()string {return e .Cause .Error () } 100 101func (e * StreamError )Unwrap ()error {return e .Cause } 102 103// CompatibilityError describes a live daemon that cannot serve this client. 104type CompatibilityError struct { 105Version int 106MissingCapabilities []string 107} 108 109func (e * CompatibilityError )Error ()string { 110if e .Version != ProtocolVersion { 111return fmt .Sprintf ("daemon protocol %d is incompatible with this client; required protocol is %d. Stop the daemon with a matching client (albedo daemon --stop), then relaunch this client" ,e .Version ,ProtocolVersion ) 112 } 113return fmt .Sprintf ("daemon protocol %d is incompatible with this client; required protocol is %d; missing capabilities: %v" ,e .Version ,ProtocolVersion ,e .MissingCapabilities ) 114} 115 116// ProtocolError identifies an invalid successful API response. 117type ProtocolError struct { 118Cause error 119Code string 120Operation string 121Field string 122} 123 124func (e * ProtocolError )Error ()string { 125if e .Field == "" { 126return fmt .Sprintf ("invalid response for %s: %v" ,e .Operation ,e .Cause ) 127 } 128return fmt .Sprintf ("invalid response for %s (%s): %v" ,e .Operation ,e .Field ,e .Cause ) 129} 130 131func (e * ProtocolError )Unwrap ()error {return e .Cause } 132 133func invalidResponse (operation operation ,field string ,cause error )error { 134return uncertainOperation (operation ,& ProtocolError {Code :"invalid_response" ,Operation :operation .Name ,Field :field ,Cause :cause }) 135} 136 137type responseFieldError struct { 138cause error 139field string 140} 141 142func (e * responseFieldError )Error ()string {return e .field + ": " + e .cause .Error () } 143 144func fieldError (field string )error { 145return & responseFieldError {field :field ,cause :errors .New ("required field is missing or invalid" )} 146}