├── .gitignore ├── LICENSE ├── README.md ├── firebase.go └── firebase_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Melvin Tercan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | firebase 2 | ======== 3 | 4 | A Go package for the Firebase API #golang 5 | 6 | ## Installation 7 | 8 | Install the package with go: 9 | 10 | ```sh 11 | go get github.com/melvinmt/firebase 12 | ``` 13 | 14 | And add it to your go file: 15 | 16 | ```go 17 | package name 18 | 19 | import ( 20 | "github.com/melvinmt/firebase" 21 | ) 22 | ``` 23 | 24 | ## Usage 25 | 26 | ```go 27 | package main 28 | 29 | import ( 30 | "github.com/melvinmt/firebase" 31 | "fmt" 32 | ) 33 | 34 | type PersonName struct { 35 | First string 36 | Last string 37 | } 38 | 39 | type Person struct { 40 | Name PersonName 41 | } 42 | 43 | func main() { 44 | var err error 45 | 46 | url := "https://SampleChat.firebaseIO-demo.com/users/fred/name" 47 | 48 | // Can also be your Firebase secret: 49 | authToken := "MqL0c8tKCtheLSYcygYNtGhU8Z2hULOFs9OKPdEp" 50 | 51 | // Auth is optional: 52 | ref := firebase.NewReference(url).Auth(authToken) 53 | 54 | // Create the value. 55 | personName := PersonName{ 56 | First: "Fred", 57 | Last: "Swanson", 58 | } 59 | 60 | // Write the value to Firebase. 61 | if err = ref.Write(personName); err != nil { 62 | panic(err) 63 | } 64 | 65 | // Now, we're going to retrieve the person. 66 | personUrl := "https://SampleChat.firebaseIO-demo.com/users/fred" 67 | 68 | personRef := firebase.NewReference(personUrl).Export(false) 69 | 70 | fred := Person{} 71 | 72 | if err = personRef.Value(fred); err != nil { 73 | panic(err) 74 | } 75 | 76 | fmt.Println(fred.Name.First, fred.Name.Last) // prints: Fred Swanson 77 | } 78 | ``` 79 | 80 | ## Docs 81 | 82 | http://godoc.org/github.com/melvinmt/firebase 83 | -------------------------------------------------------------------------------- /firebase.go: -------------------------------------------------------------------------------- 1 | /* A Go package for the Firebase API #golang 2 | */ 3 | package firebase 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | "errors" 9 | "io/ioutil" 10 | "net/http" 11 | "net/url" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | type client interface { 17 | Do(req *http.Request) (resp *http.Response, err error) 18 | } 19 | 20 | type Reference struct { 21 | url string 22 | postfix string 23 | client client 24 | token string 25 | export bool 26 | response *http.Response 27 | responseBody []byte 28 | } 29 | 30 | // Retrieve a new Firebase reference for given url. 31 | func NewReference(url string) *Reference { 32 | 33 | // Initialize Reference struct. 34 | r := &Reference{ 35 | url: url, 36 | postfix: ".json", 37 | export: false, 38 | client: &http.Client{}, 39 | } 40 | 41 | return r 42 | } 43 | 44 | // Uses the Firebase secret or Auth Token to authenticate. 45 | func (r *Reference) Auth(token string) *Reference { 46 | r.token = token 47 | 48 | return r 49 | } 50 | 51 | // Set to true if you want priority data to be returned. 52 | func (r *Reference) Export(toggle bool) *Reference { 53 | r.export = toggle 54 | 55 | return r 56 | } 57 | 58 | // Execute a new HTTP Request. 59 | func (r *Reference) executeRequest(method string, body []byte) ([]byte, error) { 60 | 61 | apiUrl := r.url + r.postfix 62 | 63 | // Build query parameters (if any). 64 | v := url.Values{} 65 | if r.token != "" { 66 | v.Set("auth", r.token) 67 | } 68 | if r.export == true { 69 | v.Set("format", "export") 70 | } 71 | q := v.Encode() 72 | 73 | // Attach query parameters to apiUrl. 74 | if len(q) > 0 { 75 | apiUrl = apiUrl + "?" + q 76 | } 77 | 78 | // Adding tiny sleep to prevent rate limited requests. 79 | time.Sleep(10 * time.Millisecond) 80 | 81 | // Prepare HTTP Request. 82 | req, err := http.NewRequest(method, apiUrl, bytes.NewReader(body)) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | // Make actual HTTP request. 88 | if r.response, err = r.client.Do(req); err != nil { 89 | return nil, err 90 | } 91 | 92 | //Poor man's 307 handling. 93 | //This code will replay the previous request using the new url in the Location header 94 | //Hopefully https://code.google.com/p/go/issues/detail?id=7912 gets resolved so we can remove this. 95 | if strings.HasPrefix(r.response.Status, "307 ") { 96 | r.response.Body.Close() // close the original 97 | 98 | location := r.response.Header.Get("Location") 99 | req, err = http.NewRequest(method, location, bytes.NewReader(body)) 100 | if r.response, err = r.client.Do(req); err != nil { 101 | return nil, err 102 | } 103 | } 104 | defer r.response.Body.Close() 105 | 106 | // Check status code for errors. 107 | status := r.response.Status 108 | if strings.HasPrefix(status, "2") == false { 109 | return nil, errors.New(status) 110 | } 111 | 112 | // Read body. 113 | if r.responseBody, err = ioutil.ReadAll(r.response.Body); err != nil { 114 | return nil, err 115 | } 116 | 117 | return r.responseBody, nil 118 | } 119 | 120 | // Retrieve the current value for this Reference. 121 | func (r *Reference) Value(v interface{}) error { 122 | 123 | // GET the data from Firebase. 124 | resp, err := r.executeRequest("GET", nil) 125 | if err != nil { 126 | return err 127 | } 128 | 129 | // JSON decode the data into given interface. 130 | err = json.Unmarshal(resp, v) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | return nil 136 | } 137 | 138 | // Set the value for this Reference (overwrites existing value). 139 | func (r *Reference) Write(v interface{}) error { 140 | 141 | // JSON encode the data. 142 | jsonData, err := json.Marshal(v) 143 | if err != nil { 144 | return err 145 | } 146 | 147 | // PUT the data to Firebase. 148 | _, err = r.executeRequest("PUT", jsonData) 149 | if err != nil { 150 | return err 151 | } 152 | 153 | return nil 154 | } 155 | 156 | // Pushes a new object to this Reference (effectively creates a list). 157 | func (r *Reference) Push(v interface{}) error { 158 | 159 | // JSON encode the data. 160 | jsonData, err := json.Marshal(v) 161 | if err != nil { 162 | return err 163 | } 164 | 165 | // POST the data to Firebase. 166 | _, err = r.executeRequest("POST", jsonData) 167 | if err != nil { 168 | return err 169 | } 170 | 171 | return nil 172 | } 173 | 174 | // Update existing data. 175 | func (r *Reference) Update(v interface{}) error { 176 | 177 | // JSON encode the data. 178 | jsonData, err := json.Marshal(v) 179 | if err != nil { 180 | return err 181 | } 182 | 183 | // PATCH the data on Firebase. 184 | _, err = r.executeRequest("PATCH", jsonData) 185 | if err != nil { 186 | return err 187 | } 188 | 189 | return nil 190 | } 191 | 192 | // Delete any values for this Reference. 193 | func (r *Reference) Delete() error { 194 | 195 | // DELETE the data on Firebase. 196 | _, err := r.executeRequest("DELETE", nil) 197 | if err != nil { 198 | return err 199 | } 200 | 201 | return nil 202 | } 203 | -------------------------------------------------------------------------------- /firebase_test.go: -------------------------------------------------------------------------------- 1 | package firebase 2 | 3 | import ( 4 | // "fmt" 5 | "bytes" 6 | "encoding/json" 7 | "io" 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | func TestThatNewReferenceWorks(t *testing.T) { 13 | url := "https://SampleChat.firebaseIO-demo.com/users/fred/name" 14 | 15 | var r *Reference 16 | r = NewReference(url) 17 | _ = r 18 | } 19 | 20 | type MockClient struct { 21 | Stub StubPerson 22 | Status string 23 | } 24 | 25 | type StubPerson struct { 26 | First string `json:"first"` 27 | Last string `json:"last"` 28 | } 29 | 30 | type readerAndCloser struct { 31 | io.Reader 32 | io.Closer 33 | } 34 | 35 | func (s StubPerson) Close() error { 36 | return nil 37 | } 38 | 39 | func (m *MockClient) Do(req *http.Request) (resp *http.Response, err error) { 40 | 41 | jsonData, err := json.Marshal(m.Stub) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | resp = &http.Response{ 47 | Status: m.Status, 48 | Body: &readerAndCloser{ 49 | bytes.NewReader(jsonData), 50 | m.Stub, 51 | }, 52 | } 53 | 54 | return resp, nil 55 | } 56 | 57 | func TestThatWriteWorks(t *testing.T) { 58 | url := "https://SampleChat.firebaseIO-demo.com/users/fred/name" 59 | 60 | var err error 61 | 62 | person := StubPerson{ 63 | First: "Fred", 64 | Last: "Swanson", 65 | } 66 | 67 | r := &Reference{ 68 | url: url, 69 | postfix: ".json", 70 | client: &MockClient{person, "200 OK"}, 71 | } 72 | 73 | err = r.Write(person) 74 | if err != nil { 75 | t.Error(err) 76 | } 77 | 78 | rn := &Reference{ 79 | url: url, 80 | postfix: ".json", 81 | client: &MockClient{person, "404 Not Found"}, 82 | } 83 | 84 | err = rn.Write(person) 85 | if err == nil { 86 | t.Error("Any status code other than 2XX should throw an error.") 87 | } 88 | } 89 | 90 | func TestThatUpdateWorks(t *testing.T) { 91 | url := "https://SampleChat.firebaseIO-demo.com/users/fred/name" 92 | 93 | var err error 94 | 95 | person := StubPerson{ 96 | First: "Fred", 97 | Last: "Swanson", 98 | } 99 | 100 | r := &Reference{ 101 | url: url, 102 | postfix: ".json", 103 | client: &MockClient{person, "200 OK"}, 104 | } 105 | 106 | err = r.Update(person) 107 | if err != nil { 108 | t.Error(err) 109 | } 110 | 111 | rn := &Reference{ 112 | url: url, 113 | postfix: ".json", 114 | client: &MockClient{person, "404 Not Found"}, 115 | } 116 | 117 | err = rn.Update(person) 118 | if err == nil { 119 | t.Error("Any status code other than 2XX should throw an error.") 120 | } 121 | } 122 | 123 | func TestThatPushWorks(t *testing.T) { 124 | url := "https://SampleChat.firebaseIO-demo.com/users/fred/name" 125 | 126 | var err error 127 | 128 | person := StubPerson{ 129 | First: "Fred", 130 | Last: "Swanson", 131 | } 132 | 133 | r := &Reference{ 134 | url: url, 135 | postfix: ".json", 136 | client: &MockClient{person, "200 OK"}, 137 | } 138 | 139 | err = r.Push(person) 140 | if err != nil { 141 | t.Error(err) 142 | } 143 | 144 | rn := &Reference{ 145 | url: url, 146 | postfix: ".json", 147 | client: &MockClient{person, "404 Not Found"}, 148 | } 149 | 150 | err = rn.Push(person) 151 | if err == nil { 152 | t.Error("Any status code other than 2XX should throw an error.") 153 | } 154 | } 155 | 156 | func TestThatDeleteWorks(t *testing.T) { 157 | url := "https://SampleChat.firebaseIO-demo.com/users/fred/name" 158 | 159 | var err error 160 | 161 | person := StubPerson{} 162 | 163 | r := &Reference{ 164 | url: url, 165 | postfix: ".json", 166 | client: &MockClient{person, "200 OK"}, 167 | } 168 | 169 | err = r.Delete() 170 | if err != nil { 171 | t.Error(err) 172 | } 173 | 174 | rn := &Reference{ 175 | url: url, 176 | postfix: ".json", 177 | client: &MockClient{person, "404 Not Found"}, 178 | } 179 | 180 | err = rn.Delete() 181 | if err == nil { 182 | t.Error("Any status code other than 2XX should throw an error.") 183 | } 184 | } 185 | 186 | func TestThatValueWorks(t *testing.T) { 187 | url := "https://SampleChat.firebaseIO-demo.com/users/fred/name" 188 | 189 | var err error 190 | 191 | person := StubPerson{ 192 | First: "Fred", 193 | Last: "Swanson", 194 | } 195 | 196 | r := &Reference{ 197 | url: url, 198 | postfix: ".json", 199 | client: &MockClient{person, "200 OK"}, 200 | } 201 | 202 | norsep := StubPerson{} 203 | 204 | err = r.Value(&norsep) 205 | if err != nil { 206 | t.Error(err) 207 | } 208 | 209 | if norsep.First != "Fred" || norsep.Last != "Swanson" { 210 | t.Error("Invalid values returned.") 211 | } 212 | 213 | rn := &Reference{ 214 | url: url, 215 | postfix: ".json", 216 | client: &MockClient{person, "404 Not Found"}, 217 | } 218 | 219 | norsep2 := StubPerson{} 220 | 221 | err = rn.Value(&norsep2) 222 | if err == nil { 223 | t.Error("Any status code other than 2XX should throw an error.") 224 | } 225 | } 226 | 227 | func TestThatAuthWorks(t *testing.T) { 228 | url := "https://SampleChat.firebaseIO-demo.com/users/fred/name" 229 | 230 | var r *Reference 231 | r = NewReference(url).Auth("token").Auth("overwrite_token") 232 | _ = r 233 | } 234 | 235 | func TestThatExportWorks(t *testing.T) { 236 | url := "https://SampleChat.firebaseIO-demo.com/users/fred/name" 237 | 238 | var r *Reference 239 | r = NewReference(url).Export(true).Export(false) 240 | _ = r 241 | } 242 | --------------------------------------------------------------------------------