Skip to content

Commit c9be1e9

Browse files
authored
Firebase Database API (#92)
* Experimental RTDB code * Added ref.Set() * Added Push(), Update(), Remove() and tests * Adding Transaction() support * Fixed Transaction() API * Code cleanup * Implemented Query() API * Added GetIfChanged() and integration tests * More integration tests * Updated unit test * More integration tests * Integration tests for queries * Auth override support and more tests * More test cases; AuthOverride support in App * Implemented AuthOverride support; Added tests * Implementing the new API * More code cleanup * Code clean up * Refactored the http client code * More tests * Boosted test coverage to 97% * Better error messages in tests; Added license headers * Added documentatioon and cleaned up tests * Fixing a build break * Finishing up documentation * More test cases * Implemented a reusable HTTP client API * Added test cases * Comment clean up * Using the shared http client API * Simplified the usage by adding HTTPClient * using the new client API * Using the old ctx import * Using the old context import * Refactored db code * More refactoring * Support for arbitrary entity types in the request * Renamed fields; Added documentation * Removing a redundant else case * Code readability improvements * Cleaned up the RTDB HTTP client code * Added shallow reads support; Added the new txn API * Implementing GetOrdered() for queries * Adding more sorting tests * Added Query ordering tests * Fixing some lint errors and compilation errors * Removing unused function * Cleaned up unit tests for db * Updated query impl and tests * Added integration tests for ordered queries * Removed With*() from query functions * Updated change log; Added more tests * Support for database url in auto init * Support for loading auth overrides from env * Removed db.AuthOverride type * Renamed ao to authOverride everywhere; Other code review nits * Introducing the QueryNode interface to handle ordered query results (#100) * Database Sample Snippets (#102) * Adding database snippets * Adding query snippets * Added complex query samples * Updated variable name * Fixing a typo * Fixing query example * Updated DB snippets to use GetOrdered() * Removing unnecessary placeholders in Fatalln() calls * Removing unnecessary placeholders in Fatalln() calls
1 parent 9d2e4a8 commit c9be1e9

25 files changed

+4634
-44
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Unreleased
22

3-
-
3+
- [added] Added the `db` package for interacting with the Firebase database.
44

55
# v2.5.0
66

auth/auth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ type signer interface {
7878
// NewClient creates a new instance of the Firebase Auth Client.
7979
//
8080
// This function can only be invoked from within the SDK. Client applications should access the
81-
// the Auth service through firebase.App.
81+
// Auth service through firebase.App.
8282
func NewClient(ctx context.Context, c *internal.AuthConfig) (*Client, error) {
8383
var (
8484
err error

db/auth_override_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright 2018 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package db
16+
17+
import (
18+
"testing"
19+
20+
"golang.org/x/net/context"
21+
)
22+
23+
func TestAuthOverrideGet(t *testing.T) {
24+
mock := &mockServer{Resp: "data"}
25+
srv := mock.Start(aoClient)
26+
defer srv.Close()
27+
28+
ref := aoClient.NewRef("peter")
29+
var got string
30+
if err := ref.Get(context.Background(), &got); err != nil {
31+
t.Fatal(err)
32+
}
33+
if got != "data" {
34+
t.Errorf("Ref(AuthOverride).Get() = %q; want = %q", got, "data")
35+
}
36+
checkOnlyRequest(t, mock.Reqs, &testReq{
37+
Method: "GET",
38+
Path: "/peter.json",
39+
Query: map[string]string{"auth_variable_override": testAuthOverrides},
40+
})
41+
}
42+
43+
func TestAuthOverrideSet(t *testing.T) {
44+
mock := &mockServer{}
45+
srv := mock.Start(aoClient)
46+
defer srv.Close()
47+
48+
ref := aoClient.NewRef("peter")
49+
want := map[string]interface{}{"name": "Peter Parker", "age": float64(17)}
50+
if err := ref.Set(context.Background(), want); err != nil {
51+
t.Fatal(err)
52+
}
53+
checkOnlyRequest(t, mock.Reqs, &testReq{
54+
Method: "PUT",
55+
Body: serialize(want),
56+
Path: "/peter.json",
57+
Query: map[string]string{"auth_variable_override": testAuthOverrides, "print": "silent"},
58+
})
59+
}
60+
61+
func TestAuthOverrideQuery(t *testing.T) {
62+
mock := &mockServer{Resp: "data"}
63+
srv := mock.Start(aoClient)
64+
defer srv.Close()
65+
66+
ref := aoClient.NewRef("peter")
67+
var got string
68+
if err := ref.OrderByChild("foo").Get(context.Background(), &got); err != nil {
69+
t.Fatal(err)
70+
}
71+
if got != "data" {
72+
t.Errorf("Ref(AuthOverride).OrderByChild() = %q; want = %q", got, "data")
73+
}
74+
checkOnlyRequest(t, mock.Reqs, &testReq{
75+
Method: "GET",
76+
Path: "/peter.json",
77+
Query: map[string]string{
78+
"auth_variable_override": testAuthOverrides,
79+
"orderBy": "\"foo\"",
80+
},
81+
})
82+
}
83+
84+
func TestAuthOverrideRangeQuery(t *testing.T) {
85+
mock := &mockServer{Resp: "data"}
86+
srv := mock.Start(aoClient)
87+
defer srv.Close()
88+
89+
ref := aoClient.NewRef("peter")
90+
var got string
91+
if err := ref.OrderByChild("foo").StartAt(1).EndAt(10).Get(context.Background(), &got); err != nil {
92+
t.Fatal(err)
93+
}
94+
if got != "data" {
95+
t.Errorf("Ref(AuthOverride).OrderByChild() = %q; want = %q", got, "data")
96+
}
97+
checkOnlyRequest(t, mock.Reqs, &testReq{
98+
Method: "GET",
99+
Path: "/peter.json",
100+
Query: map[string]string{
101+
"auth_variable_override": testAuthOverrides,
102+
"orderBy": "\"foo\"",
103+
"startAt": "1",
104+
"endAt": "10",
105+
},
106+
})
107+
}

db/db.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright 2018 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package db contains functions for accessing the Firebase Realtime Database.
16+
package db
17+
18+
import (
19+
"encoding/json"
20+
"fmt"
21+
"runtime"
22+
"strings"
23+
24+
"firebase.google.com/go/internal"
25+
26+
"net/url"
27+
28+
"golang.org/x/net/context"
29+
"google.golang.org/api/option"
30+
"google.golang.org/api/transport"
31+
)
32+
33+
const userAgentFormat = "Firebase/HTTP/%s/%s/AdminGo"
34+
const invalidChars = "[].#$"
35+
const authVarOverride = "auth_variable_override"
36+
37+
// Client is the interface for the Firebase Realtime Database service.
38+
type Client struct {
39+
hc *internal.HTTPClient
40+
url string
41+
authOverride string
42+
}
43+
44+
// NewClient creates a new instance of the Firebase Database Client.
45+
//
46+
// This function can only be invoked from within the SDK. Client applications should access the
47+
// Database service through firebase.App.
48+
func NewClient(ctx context.Context, c *internal.DatabaseConfig) (*Client, error) {
49+
opts := append([]option.ClientOption{}, c.Opts...)
50+
ua := fmt.Sprintf(userAgentFormat, c.Version, runtime.Version())
51+
opts = append(opts, option.WithUserAgent(ua))
52+
hc, _, err := transport.NewHTTPClient(ctx, opts...)
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
p, err := url.ParseRequestURI(c.URL)
58+
if err != nil {
59+
return nil, err
60+
} else if p.Scheme != "https" {
61+
return nil, fmt.Errorf("invalid database URL: %q; want scheme: %q", c.URL, "https")
62+
} else if !strings.HasSuffix(p.Host, ".firebaseio.com") {
63+
return nil, fmt.Errorf("invalid database URL: %q; want host: %q", c.URL, "firebaseio.com")
64+
}
65+
66+
var ao []byte
67+
if c.AuthOverride == nil || len(c.AuthOverride) > 0 {
68+
ao, err = json.Marshal(c.AuthOverride)
69+
if err != nil {
70+
return nil, err
71+
}
72+
}
73+
74+
ep := func(b []byte) string {
75+
var p struct {
76+
Error string `json:"error"`
77+
}
78+
if err := json.Unmarshal(b, &p); err != nil {
79+
return ""
80+
}
81+
return p.Error
82+
}
83+
return &Client{
84+
hc: &internal.HTTPClient{Client: hc, ErrParser: ep},
85+
url: fmt.Sprintf("https://%s", p.Host),
86+
authOverride: string(ao),
87+
}, nil
88+
}
89+
90+
// NewRef returns a new database reference representing the node at the specified path.
91+
func (c *Client) NewRef(path string) *Ref {
92+
segs := parsePath(path)
93+
key := ""
94+
if len(segs) > 0 {
95+
key = segs[len(segs)-1]
96+
}
97+
98+
return &Ref{
99+
Key: key,
100+
Path: "/" + strings.Join(segs, "/"),
101+
client: c,
102+
segs: segs,
103+
}
104+
}
105+
106+
func (c *Client) send(
107+
ctx context.Context,
108+
method, path string,
109+
body internal.HTTPEntity,
110+
opts ...internal.HTTPOption) (*internal.Response, error) {
111+
112+
if strings.ContainsAny(path, invalidChars) {
113+
return nil, fmt.Errorf("invalid path with illegal characters: %q", path)
114+
}
115+
if c.authOverride != "" {
116+
opts = append(opts, internal.WithQueryParam(authVarOverride, c.authOverride))
117+
}
118+
return c.hc.Do(ctx, &internal.Request{
119+
Method: method,
120+
URL: fmt.Sprintf("%s%s.json", c.url, path),
121+
Body: body,
122+
Opts: opts,
123+
})
124+
}
125+
126+
func parsePath(path string) []string {
127+
var segs []string
128+
for _, s := range strings.Split(path, "/") {
129+
if s != "" {
130+
segs = append(segs, s)
131+
}
132+
}
133+
return segs
134+
}

0 commit comments

Comments
 (0)