okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
7aba059
main
1package daemon 2 3import ( 4"bytes" 5"encoding/json" 6"errors" 7"net/http" 8"reflect" 9"strings" 10"time" 11 12"albedo/cli/internal/daemon/protocol" 13) 14 15func value [T any ](pointer * T )T { 16if pointer != nil { 17return * pointer 18 } 19var zero T 20return zero 21} 22func timestampMilliseconds (value string )* int64 { 23parsed ,err := time .Parse (time .RFC3339Nano ,value ) 24if err != nil { 25return nil 26 } 27ms := parsed .UnixMilli () 28return & ms 29} 30func timestampSeconds (value string )* int64 { 31parsed ,err := time .Parse (time .RFC3339Nano ,value ) 32if err != nil { 33return nil 34 } 35seconds := parsed .Unix () 36return & seconds 37} 38 39func intPointer (value * int64 )* int { 40if value == nil { 41return nil 42 } 43n := int (* value ) 44return & n 45} 46func observedHeaders (etag string ) (http.Header ,error ) { 47if etag == "" { 48return nil ,errors .New ("this change requires the resource's observed ETag; refresh it first" ) 49 } 50return http.Header {"If-Match" : {etag },"Content-Type" : {"application/merge-patch+json" }},nil 51} 52func decodeRequired (data []byte ,target any ,names ... string )error { 53data = bytes .TrimSpace (data ) 54if len (data )== 0 || data [0 ]!= '{' { 55return fieldError ("object" ) 56 } 57kind := reflect .TypeOf (target ).Elem () 58generated := kind .PkgPath ()== reflect .TypeFor [protocol.Session ]().PkgPath () 59if generated || len (names )> 0 { 60var tree any 61decoder := json .NewDecoder (bytes .NewReader (data )) 62decoder .UseNumber () 63if err := decoder .Decode (& tree );err != nil { 64return err 65 } 66fields ,ok := tree .(map [string ]any ) 67if ! ok { 68return fieldError ("object" ) 69 } 70for _ ,name := range names { 71if _ ,ok := fields [name ];! ok { 72return fieldError (name ) 73 } 74 } 75if generated { 76if err := validateWireValue (tree ,kind );err != nil { 77return err 78 } 79 } 80 } 81return json .Unmarshal (data ,target ) 82} 83 84// Validate one parsed tree so nested required members and nulls never become 85// Go zero values. Raw unions are decoded by their discriminated adapters. 86func validateWireValue (value any ,kind reflect.Type )error { 87if kind == reflect .TypeFor [json.RawMessage ]() { 88return nil 89 } 90if kind .Kind ()== reflect .Pointer { 91if value == nil { 92return nil 93 } 94return validateWireValue (value ,kind .Elem ()) 95 } 96if value == nil { 97return fieldError ("null " + kind .String ()) 98 } 99switch kind .Kind () { 100case reflect .Struct : 101fields ,ok := value .(map [string ]any ) 102if ! ok { 103return fieldError ("object" ) 104 } 105for field := range kind .Fields () { 106name ,options ,_ := strings .Cut (field .Tag .Get ("json" ),"," ) 107if name == "" || name == "-" { 108continue 109 } 110child ,present := fields [name ] 111if ! present { 112if ! strings .Contains (options ,"omitempty" ) { 113return fieldError (name ) 114 } 115continue 116 } 117if child == nil && field .Type != reflect .TypeFor [json.RawMessage ]() { 118if field .Tag .Get ("nullable" )!= "true" { 119return fieldError (name ) 120 } 121continue 122 } 123if err := validateWireValue (child ,field .Type );err != nil { 124return fmtField (name ,err ) 125 } 126 } 127case reflect .Slice : 128items ,ok := value .([]any ) 129if ! ok { 130return fieldError ("array" ) 131 } 132for _ ,item := range items { 133if err := validateWireValue (item ,kind .Elem ());err != nil { 134return err 135 } 136 } 137case reflect .Map : 138fields ,ok := value .(map [string ]any ) 139if ! ok { 140return fieldError ("object" ) 141 } 142for _ ,child := range fields { 143if err := validateWireValue (child ,kind .Elem ());err != nil { 144return err 145 } 146 } 147 } 148return nil 149} 150func fmtField (name string ,err error )error {return errors .New (name + ": " + err .Error ()) } 151 152// optionalText omits unset optional request fields. 153func optionalText (text string )* string { 154if text == "" { 155return nil 156 } 157return & text 158}