Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package tinybird

import "encoding/json"

// Row is a generic map-based structure that can hold fields of any type.
// Each key corresponds to a field name, and each value can be any Go type.
type Row map[string]any

// Data represents a collection (slice) of rows returned from a query or dataset.
type Data []Row

// First returns the first Row in the Data slice.
// It assumes the slice is not empty.
func (d Data) First() (out Row) {
out = d[0]

return out
}

// FetchOne retrieves the value of a specific field from the first Row
// in the Data slice, using the provided field name (key).
// If the key doesn't exist, it returns nil.
func (d Data) FetchOne(in string) (out any) {
row := d.First()
out = row[in]

return out
}

// ToString converts the entire Data slice into a JSON-formatted string.
// If marshalling fails, it returns an empty string.
func (d Data) ToString() (out string) {
tmp, _ := json.Marshal(d)

return string(tmp)
}
70 changes: 70 additions & 0 deletions data_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package tinybird

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestFirst_ReturnsFirstRow(t *testing.T) {
d := Data{
{"id": 1, "name": "first"},
{"id": 2, "name": "second"},
}

first := d.First()
assert.Equal(t, Row{"id": 1, "name": "first"}, first)
}

func TestFirst_PanicsOnEmptyData(t *testing.T) {
var d Data
assert.Panics(t, func() { _ = d.First() })

empty := Data{}
assert.Panics(t, func() { _ = empty.First() })
}

func TestFetchOne_ReturnsValueFromFirstRow(t *testing.T) {
d := Data{
{"foo": "bar", "n": 42},
{"foo": "baz"},
}

val := d.FetchOne("foo")
assert.Equal(t, "bar", val)

valNum := d.FetchOne("n")
assert.Equal(t, 42, valNum)
}

func TestFetchOne_ReturnsNilWhenKeyMissing(t *testing.T) {
d := Data{
{"foo": "bar"},
}

val := d.FetchOne("missing")
assert.Nil(t, val)
}

func TestFetchOne_PanicsOnEmptyData(t *testing.T) {
var d Data
assert.Panics(t, func() { _ = d.FetchOne("foo") })
}

func TestToString_JSONRoundTripEq(t *testing.T) {
d := Data{
{"a": 1, "b": "x"},
{"a": 2, "c": true},
}

expected := `[{"a":1,"b":"x"},{"a":2,"c":true}]`
assert.JSONEq(t, expected, d.ToString())
}

func TestToString_OnNilAndEmpty(t *testing.T) {
var dNil Data
dEmpty := Data{}

assert.Equal(t, "null", dNil.ToString())
assert.Equal(t, "[]", dEmpty.ToString())
}
6 changes: 3 additions & 3 deletions request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestRequest(t *testing.T) {
assert.Nil(t, req.Error)
assert.Equal(t, res.Status, http.StatusOK)
assert.Equal(t, res.Rows, uint(1))
assert.Equal(t, res.Data, []tinybird.Row{{"Col1": "1", "Col2": float64(2)}})
assert.Equal(t, res.Data, tinybird.Data{{"Col1": "1", "Col2": float64(2)}})
}

func TestRequestWithCustomURL(t *testing.T) {
Expand Down Expand Up @@ -66,7 +66,7 @@ func TestRequestWithCustomURL(t *testing.T) {
assert.Nil(t, req.Error)
assert.Equal(t, res.Status, http.StatusOK)
assert.Equal(t, res.Rows, uint(1))
assert.Equal(t, res.Data, []tinybird.Row{{"Col1": "1", "Col2": float64(2)}})
assert.Equal(t, res.Data, tinybird.Data{{"Col1": "1", "Col2": float64(2)}})
}

func TestRequestWithRequestParamInspect(t *testing.T) {
Expand Down Expand Up @@ -204,5 +204,5 @@ func TestRequestEvent(t *testing.T) {
assert.Nil(t, req.Error)
assert.Equal(t, res.Status, http.StatusOK)
assert.Equal(t, res.Rows, uint(0))
assert.Equal(t, res.Data, []tinybird.Row(nil))
assert.Equal(t, res.Data, tinybird.Data(nil))
}
5 changes: 1 addition & 4 deletions response.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type Response struct {
Cached bool // Flag control to use to determine the origin this response.
Raw io.ReadCloser // Raw is the original data from response.
Body string // Body in string format, same a Data but in struct format and Raw in original format.
Data []Row `json:"data,omitempty"` // Data is part a tinybird response.
Data Data `json:"data,omitempty"` // Data is part a tinybird response.
Documentation string `json:"documentation,omitempty"` // Documentation is part a tinybird response.
Error string `json:"error,omitempty"` // Error is part a tinybird response.
Meta []Meta `json:"meta,omitempty"` // Meta is part a tinybird response.
Expand All @@ -28,9 +28,6 @@ type Response struct {
QuarantinedRows int `json:"quarantined_rows"`
}

// Generic row structure to allow any field with any type.
type Row map[string]any

// Specific field with data type.
type Meta struct {
Name string `json:"name,omitempty"`
Expand Down