added new geoPoint field

This commit is contained in:
Gani Georgiev
2025-04-02 11:38:19 +03:00
parent f3a836eb7c
commit 4c5abd5bd9
60 changed files with 1373 additions and 1143 deletions
+75
View File
@@ -0,0 +1,75 @@
package types
import (
"database/sql/driver"
"encoding/json"
"fmt"
)
// GeoPoint defines a struct for storing geo coordinates as serialized json object
// (e.g. {lon:0,lat:0}).
//
// Note: using object notation and not a plain array to avoid the confusion
// as there doesn't seem to be a fixed standard for the coordinates order.
type GeoPoint struct {
Lon float64 `form:"lon" json:"lon"`
Lat float64 `form:"lat" json:"lat"`
}
// String returns the string representation of the current GeoPoint instance.
func (p GeoPoint) String() string {
raw, _ := json.Marshal(p)
return string(raw)
}
// Value implements the [driver.Valuer] interface.
func (p GeoPoint) Value() (driver.Value, error) {
data, err := json.Marshal(p)
return string(data), err
}
// Scan implements [sql.Scanner] interface to scan the provided value
// into the current GeoPoint instance.
//
// The value argument could be nil (no-op), another GeoPoint instance,
// map or serialized json object with lat-lon props.
func (p *GeoPoint) Scan(value any) error {
var err error
switch v := value.(type) {
case nil:
// no cast needed
case *GeoPoint:
p.Lon = v.Lon
p.Lat = v.Lat
case GeoPoint:
p.Lon = v.Lon
p.Lat = v.Lat
case JSONRaw:
if len(v) != 0 {
err = json.Unmarshal(v, p)
}
case []byte:
if len(v) != 0 {
err = json.Unmarshal(v, p)
}
case string:
if len(v) != 0 {
err = json.Unmarshal([]byte(v), p)
}
default:
var raw []byte
raw, err = json.Marshal(v)
if err != nil {
err = fmt.Errorf("unable to marshalize value for scanning: %w", err)
} else {
err = json.Unmarshal(raw, p)
}
}
if err != nil {
return fmt.Errorf("[GeoPoint] unable to scan value %v: %w", value, err)
}
return nil
}
+81
View File
@@ -0,0 +1,81 @@
package types_test
import (
"fmt"
"testing"
"github.com/pocketbase/pocketbase/tools/types"
)
func TestGeoPointStringAndValue(t *testing.T) {
t.Parallel()
scenarios := []struct {
name string
point types.GeoPoint
expected string
}{
{"zero", types.GeoPoint{}, `{"lon":0,"lat":0}`},
{"non-zero", types.GeoPoint{Lon: -10, Lat: 20.123}, `{"lon":-10,"lat":20.123}`},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
str := s.point.String()
val, err := s.point.Value()
if err != nil {
t.Fatal(err)
}
if str != val {
t.Fatalf("Expected String and Value to return the same value")
}
if str != s.expected {
t.Fatalf("Expected\n%s\ngot\n%s", s.expected, str)
}
})
}
}
func TestGeoPointScan(t *testing.T) {
t.Parallel()
scenarios := []struct {
value any
expectErr bool
expectStr string
}{
{nil, false, `{"lon":1,"lat":2}`},
{"", false, `{"lon":1,"lat":2}`},
{types.JSONRaw{}, false, `{"lon":1,"lat":2}`},
{[]byte{}, false, `{"lon":1,"lat":2}`},
{`{}`, false, `{"lon":1,"lat":2}`},
{`[]`, true, `{"lon":1,"lat":2}`},
{0, true, `{"lon":1,"lat":2}`},
{`{"lon":1.23,"lat":4.56}`, false, `{"lon":1.23,"lat":4.56}`},
{[]byte(`{"lon":1.23,"lat":4.56}`), false, `{"lon":1.23,"lat":4.56}`},
{types.JSONRaw(`{"lon":1.23,"lat":4.56}`), false, `{"lon":1.23,"lat":4.56}`},
{types.GeoPoint{}, false, `{"lon":0,"lat":0}`},
{types.GeoPoint{Lon: 1.23, Lat: 4.56}, false, `{"lon":1.23,"lat":4.56}`},
{&types.GeoPoint{Lon: 1.23, Lat: 4.56}, false, `{"lon":1.23,"lat":4.56}`},
}
for i, s := range scenarios {
t.Run(fmt.Sprintf("%d_%#v", i, s.value), func(t *testing.T) {
point := types.GeoPoint{Lon: 1, Lat: 2}
err := point.Scan(s.value)
hasErr := err != nil
if hasErr != s.expectErr {
t.Errorf("Expected hasErr %v, got %v (%v)", s.expectErr, hasErr, err)
}
if str := point.String(); str != s.expectStr {
t.Errorf("Expected\n%s\ngot\n%s", s.expectStr, str)
}
})
}
}