feat(graphql): add JS-safe uint64 marshal helpers
Adds MarshalUint64, UnmarshalUint64, and Uint64Marshaler for gqlgen- compatible uint64 string scalars without a gqlgen dependency. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
- `WithTxValue` for transactional functions that return a value.
|
||||
- `QueryCount`, `BuildQueryCountFn`, and `CountResult` for pagination counts.
|
||||
- `BinaryKSUID.BinExpr`, `ExprStringKSUIDs`, and flexible `Parse*Any` KSUID parsers.
|
||||
- `MarshalUint64` and `UnmarshalUint64` for JS-safe GraphQL uint64 scalars.
|
||||
- Context-aware variants for all query and mutation helpers.
|
||||
- `Delete`, `DeleteAffected`, `WithTx`, `ContainsCol`, and `CurrentDialect()`.
|
||||
- Package documentation (`doc.go`), expanded README, and subpackage docs for `dbxm` / `dbxp`.
|
||||
|
||||
@@ -49,6 +49,7 @@ For Postgres, use `dbx.DialectPostgres` and blank-import `dbxp` instead of `dbxm
|
||||
| Partial update | `ApplyPtr`, `ApplyComplexPtr`, `ApplyInterfacePtr`, `ApplyVal` |
|
||||
| Pointers | `Ptr`, `Val`, `NowPtr`, `TrimPtr`, `TrimPtrToNil`, `IsZero` |
|
||||
| Types | `StringKSUID`, `BinaryKSUID`, `JSONB` |
|
||||
| GraphQL | `MarshalUint64`, `UnmarshalUint64` |
|
||||
|
||||
## KSUID type selection
|
||||
|
||||
|
||||
@@ -33,6 +33,11 @@
|
||||
//
|
||||
// [JSONB] provides map-based JSON column scanning for Postgres JSONB and MySQL JSON.
|
||||
//
|
||||
// # GraphQL scalars
|
||||
//
|
||||
// [MarshalUint64] and [UnmarshalUint64] provide JS-safe uint64 serialization for
|
||||
// gqlgen without importing gqlgen directly.
|
||||
//
|
||||
// # Pointer and string utilities
|
||||
//
|
||||
// [Ptr], [Val], [TrimPtr], [StringToFilter], and related helpers for nullable
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package dbx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Uint64Marshaler wraps a uint64 for GraphQL JSON output as a string scalar.
|
||||
type Uint64Marshaler uint64
|
||||
|
||||
// MarshalGQL writes the uint64 as a JSON string for gqlgen compatibility.
|
||||
func (u Uint64Marshaler) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprintf(w, "\"%d\"", uint64(u))
|
||||
}
|
||||
|
||||
// MarshalUint64 marshals a uint64 as a JSON string to avoid JavaScript
|
||||
// precision loss for integers larger than 2^53-1.
|
||||
func MarshalUint64(i uint64) Uint64Marshaler {
|
||||
return Uint64Marshaler(i)
|
||||
}
|
||||
|
||||
// UnmarshalUint64 unmarshals a uint64 from a JSON string or number.
|
||||
func UnmarshalUint64(v any) (uint64, error) {
|
||||
switch value := v.(type) {
|
||||
case string:
|
||||
if value == "" {
|
||||
return 0, nil
|
||||
}
|
||||
n, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid uint64 string: %w", err)
|
||||
}
|
||||
return n, nil
|
||||
case int:
|
||||
if value < 0 {
|
||||
return 0, fmt.Errorf("invalid negative uint64: %d", value)
|
||||
}
|
||||
return uint64(value), nil
|
||||
case int64:
|
||||
if value < 0 {
|
||||
return 0, fmt.Errorf("invalid negative uint64: %d", value)
|
||||
}
|
||||
return uint64(value), nil
|
||||
case float64:
|
||||
if value < 0 || value != float64(uint64(value)) {
|
||||
return 0, fmt.Errorf("invalid uint64 number: %v", value)
|
||||
}
|
||||
return uint64(value), nil
|
||||
case uint64:
|
||||
return value, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid uint64 type: %T", v)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package dbx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMarshalUint64(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
MarshalUint64(9007199254740993).MarshalGQL(&buf)
|
||||
assert.Equal(t, `"9007199254740993"`, buf.String())
|
||||
}
|
||||
|
||||
func TestUnmarshalUint64_String(t *testing.T) {
|
||||
n, err := UnmarshalUint64("42")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint64(42), n)
|
||||
}
|
||||
|
||||
func TestUnmarshalUint64_Number(t *testing.T) {
|
||||
n, err := UnmarshalUint64(float64(42))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint64(42), n)
|
||||
}
|
||||
|
||||
func TestUnmarshalUint64_RoundTripJSON(t *testing.T) {
|
||||
const original uint64 = 9007199254740993
|
||||
var buf bytes.Buffer
|
||||
MarshalUint64(original).MarshalGQL(&buf)
|
||||
|
||||
var wire string
|
||||
require.NoError(t, json.Unmarshal(buf.Bytes(), &wire))
|
||||
|
||||
got, err := UnmarshalUint64(wire)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, original, got)
|
||||
}
|
||||
|
||||
func TestUnmarshalUint64_Invalid(t *testing.T) {
|
||||
_, err := UnmarshalUint64(true)
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = UnmarshalUint64(float64(-1))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
Reference in New Issue
Block a user