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  }