github.com/letsencrypt/boulder@v0.20251208.0/sa/type-converter.go (about) 1 package sa 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "time" 8 9 "github.com/go-jose/go-jose/v4" 10 11 "github.com/letsencrypt/borp" 12 13 "github.com/letsencrypt/boulder/core" 14 "github.com/letsencrypt/boulder/identifier" 15 ) 16 17 // BoulderTypeConverter is used by borp for storing objects in DB. 18 type BoulderTypeConverter struct{} 19 20 // ToDb converts a Boulder object to one suitable for the DB representation. 21 func (tc BoulderTypeConverter) ToDb(val any) (any, error) { 22 switch t := val.(type) { 23 case identifier.ACMEIdentifier, []core.Challenge, []string, [][]int: 24 jsonBytes, err := json.Marshal(t) 25 if err != nil { 26 return nil, err 27 } 28 return string(jsonBytes), nil 29 case jose.JSONWebKey: 30 jsonBytes, err := t.MarshalJSON() 31 if err != nil { 32 return "", err 33 } 34 return string(jsonBytes), nil 35 case core.AcmeStatus: 36 return string(t), nil 37 case core.OCSPStatus: 38 return string(t), nil 39 // Time types get truncated to the nearest second. Given our DB schema, 40 // only seconds are stored anyhow. Avoiding sending queries with sub-second 41 // precision may help the query planner avoid pathological cases when 42 // querying against indexes on time fields (#5437). 43 case time.Time: 44 return t.Truncate(time.Second), nil 45 case *time.Time: 46 if t == nil { 47 return nil, nil 48 } 49 newT := t.Truncate(time.Second) 50 return &newT, nil 51 default: 52 return val, nil 53 } 54 } 55 56 // FromDb converts a DB representation back into a Boulder object. 57 func (tc BoulderTypeConverter) FromDb(target any) (borp.CustomScanner, bool) { 58 switch target.(type) { 59 case *identifier.ACMEIdentifier, *[]core.Challenge, *[]string, *[][]int: 60 binder := func(holder, target any) error { 61 s, ok := holder.(*string) 62 if !ok { 63 return errors.New("FromDb: Unable to convert *string") 64 } 65 b := []byte(*s) 66 err := json.Unmarshal(b, target) 67 if err != nil { 68 return badJSONError( 69 fmt.Sprintf("binder failed to unmarshal %T", target), 70 b, 71 err) 72 } 73 return nil 74 } 75 return borp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true 76 case *jose.JSONWebKey: 77 binder := func(holder, target any) error { 78 s, ok := holder.(*string) 79 if !ok { 80 return fmt.Errorf("FromDb: Unable to convert %T to *string", holder) 81 } 82 if *s == "" { 83 return errors.New("FromDb: Empty JWK field.") 84 } 85 b := []byte(*s) 86 k, ok := target.(*jose.JSONWebKey) 87 if !ok { 88 return fmt.Errorf("FromDb: Unable to convert %T to *jose.JSONWebKey", target) 89 } 90 err := k.UnmarshalJSON(b) 91 if err != nil { 92 return badJSONError( 93 "binder failed to unmarshal JWK", 94 b, 95 err) 96 } 97 return nil 98 } 99 return borp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true 100 case *core.AcmeStatus: 101 binder := func(holder, target any) error { 102 s, ok := holder.(*string) 103 if !ok { 104 return fmt.Errorf("FromDb: Unable to convert %T to *string", holder) 105 } 106 st, ok := target.(*core.AcmeStatus) 107 if !ok { 108 return fmt.Errorf("FromDb: Unable to convert %T to *core.AcmeStatus", target) 109 } 110 111 *st = core.AcmeStatus(*s) 112 return nil 113 } 114 return borp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true 115 case *core.OCSPStatus: 116 binder := func(holder, target any) error { 117 s, ok := holder.(*string) 118 if !ok { 119 return fmt.Errorf("FromDb: Unable to convert %T to *string", holder) 120 } 121 st, ok := target.(*core.OCSPStatus) 122 if !ok { 123 return fmt.Errorf("FromDb: Unable to convert %T to *core.OCSPStatus", target) 124 } 125 126 *st = core.OCSPStatus(*s) 127 return nil 128 } 129 return borp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true 130 default: 131 return borp.CustomScanner{}, false 132 } 133 }