github.com/mitranim/sqlb@v0.7.2/sqlb_text.go (about) 1 package sqlb 2 3 import ( 4 "encoding" 5 "fmt" 6 r "reflect" 7 "strconv" 8 ) 9 10 /* 11 Tiny shortcut for encoding an `AppenderTo` implementation to a string by using its 12 `.AppendTo` method, without paying for a string-to-byte conversion. Used 13 internally by many `Expr` implementations. Exported because it's handy for 14 defining new types. 15 */ 16 func AppenderString(val AppenderTo) string { 17 if val != nil { 18 return bytesToMutableString(val.AppendTo(nil)) 19 } 20 return `` 21 } 22 23 // Variant of `String` that panics on error. 24 func TryString(val any) string { return try1(String(val)) } 25 26 /* 27 Missing feature of the standard library: return a string representation of an 28 arbitrary value intended only for machine use, only for "intentionally" 29 encodable types, without swallowing errors. Differences from `fmt.Sprint`: 30 31 * Nil input = "" output. 32 33 * Returns errors separately, without encoding them into the output. This is 34 important when the output is intended to be passed to another system rather 35 than read by humans. 36 37 * Supports ONLY the following types, in this order of priority. For other 38 types, returns an error. 39 40 * `fmt.Stringer` 41 * `AppenderTo` 42 * `encoding.TextMarshaler` 43 * Built-in primitive types. 44 * Encodes floats without the scientific notation. 45 * Aliases of `[]byte`. 46 */ 47 func String(src any) (string, error) { 48 if src == nil { 49 return ``, nil 50 } 51 52 stringer, _ := src.(fmt.Stringer) 53 if stringer != nil { 54 return stringer.String(), nil 55 } 56 57 appender, _ := src.(AppenderTo) 58 if appender != nil { 59 return bytesToMutableString(appender.AppendTo(nil)), nil 60 } 61 62 marshaler, _ := src.(encoding.TextMarshaler) 63 if marshaler != nil { 64 chunk, err := marshaler.MarshalText() 65 str := bytesToMutableString(chunk) 66 if err != nil { 67 return ``, ErrInternal{Err{`generating string representation`, err}} 68 } 69 return str, nil 70 } 71 72 typ := typeOf(src) 73 val := valueOf(src) 74 75 switch typ.Kind() { 76 case r.Int8, r.Int16, r.Int32, r.Int64, r.Int: 77 if val.IsValid() { 78 return strconv.FormatInt(val.Int(), 10), nil 79 } 80 return ``, nil 81 82 case r.Uint8, r.Uint16, r.Uint32, r.Uint64, r.Uint: 83 if val.IsValid() { 84 return strconv.FormatUint(val.Uint(), 10), nil 85 } 86 return ``, nil 87 88 case r.Float32, r.Float64: 89 if val.IsValid() { 90 return strconv.FormatFloat(val.Float(), 'f', -1, 64), nil 91 } 92 return ``, nil 93 94 case r.Bool: 95 if val.IsValid() { 96 return strconv.FormatBool(val.Bool()), nil 97 } 98 return ``, nil 99 100 case r.String: 101 if val.IsValid() { 102 return val.String(), nil 103 } 104 return ``, nil 105 106 default: 107 if typ.ConvertibleTo(typeBytes) { 108 if val.IsValid() { 109 return bytesToMutableString(val.Bytes()), nil 110 } 111 return ``, nil 112 } 113 114 return ``, errUnsupportedType(`generating string representation`, typ) 115 } 116 } 117 118 // Variant of `AppendTo` that panics on error. 119 func TryAppend(buf []byte, src any) []byte { return try1(AppendTo(buf, src)) } 120 121 /* 122 Missing feature of the standard library: append the text representation of an 123 arbitrary value to the buffer, prioritizing "append"-style encoding functions 124 over "string"-style functions for efficiency, using only "intentional" 125 representations, and without swallowing errors. 126 127 Supports ONLY the following types, in this order of priority. For other types, 128 returns an error. 129 130 * `AppenderTo` 131 * `encoding.TextMarshaler` 132 * `fmt.Stringer` 133 * Built-in primitive types. 134 * Aliases of `[]byte`. 135 136 Special cases: 137 138 * Nil: append nothing, return buffer as-is. 139 * Integers: use `strconv.AppendInt` in base 10. 140 * Floats: use `strconv.AppendFloat` without scientific notation. 141 142 Used internally by `CommaAppender`, exported for advanced users. 143 */ 144 func AppendTo(buf []byte, src any) ([]byte, error) { 145 if src == nil { 146 return buf, nil 147 } 148 149 appender, _ := src.(AppenderTo) 150 if appender != nil { 151 return appender.AppendTo(buf), nil 152 } 153 154 marshaler, _ := src.(encoding.TextMarshaler) 155 if marshaler != nil { 156 chunk, err := marshaler.MarshalText() 157 if err != nil { 158 return buf, ErrInternal{Err{`appending string representation`, err}} 159 } 160 return append(buf, chunk...), nil 161 } 162 163 stringer, _ := src.(fmt.Stringer) 164 if stringer != nil { 165 return append(buf, stringer.String()...), nil 166 } 167 168 typ := typeOf(src) 169 val := valueOf(src) 170 171 switch typ.Kind() { 172 case r.Int8, r.Int16, r.Int32, r.Int64, r.Int: 173 if val.IsValid() { 174 return strconv.AppendInt(buf, val.Int(), 10), nil 175 } 176 return buf, nil 177 178 case r.Uint8, r.Uint16, r.Uint32, r.Uint64, r.Uint: 179 if val.IsValid() { 180 return strconv.AppendUint(buf, val.Uint(), 10), nil 181 } 182 return buf, nil 183 184 case r.Float32, r.Float64: 185 if val.IsValid() { 186 return strconv.AppendFloat(buf, val.Float(), 'f', -1, 64), nil 187 } 188 return buf, nil 189 190 case r.Bool: 191 if val.IsValid() { 192 return strconv.AppendBool(buf, val.Bool()), nil 193 } 194 return buf, nil 195 196 case r.String: 197 if val.IsValid() { 198 return append(buf, val.String()...), nil 199 } 200 return buf, nil 201 202 default: 203 if typ.ConvertibleTo(typeBytes) { 204 if val.IsValid() { 205 return append(buf, val.Bytes()...), nil 206 } 207 return buf, nil 208 } 209 210 return buf, errUnsupportedType(`appending string representation`, typ) 211 } 212 } 213 214 // Variant of `AppendWith` that panics on error. 215 func TryAppendWith(buf *[]byte, delim string, val any) bool { 216 return try1(AppendWith(buf, delim, val)) 217 } 218 219 /* 220 Attempts to append the given delimiter and the text representation of the given 221 value, via `AppendTo`. If after delimiter non-zero amount of bytes was appended, 222 returns true. Otherwise reverts the buffer to the original length and returns 223 false. If the buffer got reallocated with increased capacity, preserves the new 224 capacity. 225 */ 226 func AppendWith(buf *[]byte, delim string, val any) (bool, error) { 227 if buf == nil { 228 return false, nil 229 } 230 231 pre := len(*buf) 232 *buf = append(*buf, delim...) 233 234 mid := len(*buf) 235 out, err := AppendTo(*buf, val) 236 if err != nil { 237 return false, err 238 } 239 *buf = out 240 241 /** 242 Note: there's a difference between snapshotting the length to reslice the 243 buffer after the append, versus naively snapshotting the slice itself. The 244 append may allocate additional capacity, perform a copy, and return a larger 245 buffer. Reslicing preserves the new capacity, which is important for 246 avoiding a "hot split" where the added capacity is repeatedly discarded. 247 */ 248 if mid == len(*buf) { 249 *buf = (*buf)[:pre] 250 return false, nil 251 } 252 return true, nil 253 }