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  }