├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── adafruitio.go ├── client.go ├── client_test.go ├── data_service.go ├── data_service_test.go ├── doc.go ├── docs └── TODO ├── example_interface_test.go ├── examples ├── data │ └── all_actions.go ├── debug │ └── request_viewer.go ├── feed_browser │ └── server.go ├── feeds │ └── all_actions.go └── groups │ └── all_actions.go ├── feed_service.go ├── feed_service_test.go ├── go.mod ├── go.sum ├── group_service.go └── group_service_test.go /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/go/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.16, 1.17, 1-bullseye, 1.16-bullseye, 1.17-bullseye, 1-buster, 1.16-buster, 1.17-buster 4 | ARG VARIANT="1.18-bullseye" 5 | FROM mcr.microsoft.com/vscode/devcontainers/go:0-${VARIANT} 6 | 7 | # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 8 | ARG NODE_VERSION="none" 9 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 10 | 11 | # [Optional] Uncomment this section to install additional OS packages. 12 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 13 | # && apt-get -y install --no-install-recommends 14 | 15 | # [Optional] Uncomment the next lines to use go get to install anything else you need 16 | # USER vscode 17 | # RUN go get -x 18 | 19 | # [Optional] Uncomment this line to install global node packages. 20 | # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 21 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/go 3 | { 4 | "name": "Go", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | "args": { 8 | // Update the VARIANT arg to pick a version of Go: 1, 1.18, 1.17 9 | // Append -bullseye or -buster to pin to an OS version. 10 | // Use -bullseye variants on local arm64/Apple Silicon. 11 | "VARIANT": "1.18-bullseye", 12 | // Options 13 | "NODE_VERSION": "none" 14 | } 15 | }, 16 | "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], 17 | 18 | // Set *default* container specific settings.json values on container create. 19 | "settings": { 20 | "go.toolsManagement.checkForUpdates": "local", 21 | "go.useLanguageServer": true, 22 | "go.gopath": "/go" 23 | }, 24 | 25 | // Add the IDs of extensions you want installed when the container is created. 26 | "extensions": [ 27 | "golang.Go" 28 | ], 29 | 30 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 31 | // "forwardPorts": [], 32 | 33 | // Use 'postCreateCommand' to run commands after the container is created. 34 | // "postCreateCommand": "go version", 35 | 36 | // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 37 | "remoteUser": "vscode" 38 | } 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/go 2 | 3 | ### Go ### 4 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 5 | *.o 6 | *.a 7 | *.so 8 | 9 | # Folders 10 | _obj 11 | _test 12 | 13 | # Architecture specific extensions/prefixes 14 | *.[568vq] 15 | [568vq].out 16 | 17 | *.cgo1.go 18 | *.cgo2.c 19 | _cgo_defun.c 20 | _cgo_gotypes.go 21 | _cgo_export.* 22 | 23 | _testmain.go 24 | 25 | *.exe 26 | *.test 27 | *.prof 28 | 29 | .profile 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | 4 | go: 5 | - 1.18 6 | matrix: 7 | include: 8 | - go: 1.18 9 | script: 10 | - go get -t -v ./... 11 | - go test -v -race ./... 12 | fast_finish: true 13 | install: 14 | - # this will be handled by the script command below 15 | script: 16 | # install, confirm formatting, "report suspicious constructs", and test 17 | - go get -t -v ./... 18 | - diff -u <(echo -n) <(gofmt -d -s .) 19 | - go tool vet ./... 20 | - go test -v -race ./... 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016- Adafruit Author: Adam Bachman 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # talk to adafruit io from Go 2 | 3 | [![GoDoc](http://godoc.org/github.com/adafruit/io-client-go?status.svg)](http://godoc.org/github.com/adafruit/io-client-go) 4 | [![Build Status](https://travis-ci.org/adafruit/io-client-go.svg?branch=master)](https://travis-ci.org/adafruit/io-client-go) 5 | 6 | A go client library for talking to your io.adafruit.com account. 7 | 8 | Requires go version 1.18 or better. Running tests uses the github.com/stretchr/testify library. To run tests, run: 9 | 10 | ```bash 11 | $ go test ./... 12 | ``` 13 | 14 | ## Usage 15 | 16 | First, add the package: 17 | ```bash 18 | $ go get github.com/adafruit/io-client-go/v2 19 | ``` 20 | 21 | Then import it: 22 | ```go 23 | import "github.com/adafruit/io-client-go/v2" 24 | ``` 25 | 26 | The io-client-go repository provides the `adafruitio` package. 27 | 28 | Authentication for Adafruit IO is managed by providing your Adafruit IO username and token. The token is sent in the head of all web requests via the `X-AIO-Key` header. This is handled for 29 | you by the client library, which expects you API Token when it is initialized. 30 | 31 | We recommend keeping the Token in an environment variable to avoid including it 32 | directly in your code. 33 | 34 | ```go 35 | client := adafruitio.NewClient(os.Getenv("ADAFRUIT_IO_USERNAME"), os.Getenv("ADAFRUIT_IO_KEY")) 36 | feeds, _, err := adafruitio.Feed.All() 37 | ``` 38 | 39 | Some API calls expect parameters, which must be provided when making the call. 40 | 41 | ```go 42 | feed := &aio.Feed{Name: "my-new-feed"} 43 | feed := client.Feed.Create(newFeed) 44 | ``` 45 | 46 | Data related API calls expect a Feed to be set before the call is made. 47 | 48 | 49 | ```go 50 | feed, _, ferr := client.Feed.Get("my-new-feed") 51 | client.SetFeed(feed) 52 | client.Data.Create(&adafruitio.Data{Value: 100}) 53 | ``` 54 | 55 | More detailed example usage can be found in the [./examples](./examples) directory 56 | 57 | For full package documentation, visit the godoc page at https://godoc.org/github.com/adafruit/io-client-go 58 | 59 | ## License 60 | 61 | Copyright (c) 2016 Adafruit Industries. Licensed under the MIT license. 62 | 63 | ## Contributing 64 | 65 | - Fork it ( http://github.com/adafruit/io-client-go/fork ) 66 | - Create your feature branch (git checkout -b my-new-feature) 67 | - Commit your changes (git commit -am 'Add some feature') 68 | - Push to the branch (git push origin my-new-feature) 69 | - Create new Pull Request 70 | 71 | --- 72 | 73 | [adafruit](https://adafruit.com) invests time and resources providing this open source code. please support adafruit and open-source hardware by purchasing products from [adafruit](https://adafruit.com). 74 | -------------------------------------------------------------------------------- /adafruitio.go: -------------------------------------------------------------------------------- 1 | // Package adafruit provides bindings for the io.adafruit.com APIs 2 | 3 | package adafruitio 4 | 5 | const ( 6 | Version = "2.0.0" 7 | ) 8 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | // Portions of code in this file are adapted from the go-github 2 | // project located at https://github.com/google/go-github 3 | 4 | package adafruitio 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "net/http" 13 | "net/url" 14 | "reflect" 15 | "runtime" 16 | 17 | "github.com/google/go-querystring/query" 18 | ) 19 | 20 | const ( 21 | BaseURL = "https://io.adafruit.com" 22 | APIPath = "/api/v2" 23 | xAIOKeyHeader = "X-AIO-Key" 24 | ) 25 | 26 | type Client struct { 27 | // Base HTTP client used to talk to io.adafruit.com 28 | client *http.Client 29 | 30 | // Base URL for API requests. Defaults to public adafruit io URL. 31 | baseURL *url.URL 32 | 33 | apiKey string 34 | username string 35 | userAgent string 36 | 37 | // Services that make up adafruit io. 38 | Data *DataService 39 | Feed *FeedService 40 | Group *GroupService 41 | } 42 | 43 | // Response wraps http.Response and adds fields unique to Adafruit's API. 44 | type Response struct { 45 | *http.Response 46 | } 47 | 48 | func (r *Response) Debug() { 49 | all, _ := ioutil.ReadAll(r.Body) 50 | fmt.Println("---") 51 | fmt.Println(string(all)) 52 | fmt.Println("---") 53 | } 54 | 55 | type AIOError struct { 56 | Message string `json:"error"` 57 | } 58 | 59 | // ErrorResponse reports one or more errors caused by an API request. 60 | type ErrorResponse struct { 61 | Response *http.Response // HTTP response that carried the error message 62 | Message string 63 | AIOError *AIOError 64 | } 65 | 66 | func (r *ErrorResponse) Error() string { 67 | return fmt.Sprintf( 68 | "%v %v %v: %v", 69 | r.Response.Request.Method, 70 | r.Response.Request.URL, 71 | r.Response.StatusCode, 72 | r.Message, 73 | ) 74 | } 75 | 76 | func NewClient(username, key string) *Client { 77 | c := &Client{username: username, apiKey: key} 78 | 79 | c.SetBaseURL(BaseURL) 80 | c.userAgent = fmt.Sprintf("AdafruitIO-Go/%v (%v %v)", Version, runtime.GOOS, runtime.Version()) 81 | 82 | c.client = http.DefaultClient 83 | 84 | c.Data = &DataService{client: c} 85 | c.Feed = &FeedService{client: c} 86 | c.Group = &GroupService{client: c} 87 | 88 | return c 89 | } 90 | 91 | // SetBaseURL updates the base URL to use. Mainly here for use in unit testing 92 | func (c *Client) SetBaseURL(baseURL string) { 93 | c.baseURL, _ = url.Parse(fmt.Sprintf("%s%s/%s/", baseURL, APIPath, c.username)) 94 | } 95 | 96 | func (c *Client) GetUserKey() (username string, apikey string) { 97 | return c.username, c.apiKey 98 | } 99 | 100 | // SetFeed takes a Feed record as a parameter and uses that feed for all 101 | // subsequent Data related API calls. 102 | // 103 | // A Feed must be set before making calls to the Data service. 104 | func (c *Client) SetFeed(feed *Feed) { 105 | c.Feed.CurrentFeed = feed 106 | } 107 | 108 | func (c *Client) checkFeed() error { 109 | if c.Feed.CurrentFeed == nil { 110 | return fmt.Errorf("CurrentFeed must be set") 111 | } 112 | return nil 113 | } 114 | 115 | // CheckResponse checks the API response for errors, and returns them if 116 | // present. A response is considered an error if it has a status code outside 117 | // the 200 range. 118 | // 119 | // adapted from https://github.com/google/go-github 120 | func CheckResponse(r *http.Response) error { 121 | if c := r.StatusCode; 200 <= c && c <= 299 { 122 | return nil 123 | } 124 | errorResponse := &ErrorResponse{Response: r} 125 | 126 | // read response body into Error.Message 127 | body, _ := ioutil.ReadAll(r.Body) 128 | 129 | // try to unmarshal error response Body into AIOError record 130 | jerr := json.Unmarshal(body, &errorResponse.AIOError) 131 | if jerr != nil { 132 | fmt.Println("> failed to parse response body as JSON") 133 | // failed to unmarhsal API Error, use body as Message 134 | errorResponse.Message = string(body) 135 | } else { 136 | fmt.Println("> parsed response body as JSON", errorResponse.AIOError) 137 | errorResponse.Message = errorResponse.AIOError.Message 138 | } 139 | 140 | return errorResponse 141 | } 142 | 143 | // NewRequest creates an API request. A relative URL can be provided in urlStr, 144 | // in which case it is resolved relative to the BaseURL of the Client. 145 | // Relative URLs should always be specified without a preceding slash. If 146 | // specified, the value pointed to by body is JSON encoded and included as the 147 | // request body. 148 | // 149 | // adapted from https://github.com/google/go-github 150 | func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) { 151 | rel, err := url.Parse(urlStr) 152 | if err != nil { 153 | return nil, err 154 | } 155 | 156 | u := c.baseURL.ResolveReference(rel) 157 | 158 | var buf io.ReadWriter 159 | if body != nil { 160 | buf = new(bytes.Buffer) 161 | err := json.NewEncoder(buf).Encode(body) 162 | if err != nil { 163 | return nil, err 164 | } 165 | } 166 | 167 | req, err := http.NewRequest(method, u.String(), buf) 168 | if err != nil { 169 | return nil, err 170 | } 171 | 172 | req.Header.Add("Accept", "application/json") 173 | req.Header.Add("Content-Type", "application/json") 174 | 175 | // Authentication v1 176 | req.Header.Add(xAIOKeyHeader, c.apiKey) 177 | 178 | if c.userAgent != "" { 179 | req.Header.Add("User-Agent", c.userAgent) 180 | } 181 | 182 | return req, nil 183 | } 184 | 185 | // Do sends an API request and returns the API response. The API response is 186 | // JSON decoded and stored in the value pointed to by v, or returned as an 187 | // error if an API error has occurred. If v implements the io.Writer 188 | // interface, the raw response body will be written to v, without attempting to 189 | // first decode it. 190 | // 191 | // adapted from https://github.com/google/go-github 192 | func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { 193 | 194 | resp, err := c.client.Do(req) 195 | if err != nil { 196 | return nil, err 197 | } 198 | 199 | defer func() { 200 | // Drain up to 512 bytes and close the body to let the Transport reuse the connection 201 | io.CopyN(ioutil.Discard, resp.Body, 512) 202 | resp.Body.Close() 203 | }() 204 | 205 | response := &Response{ 206 | Response: resp, 207 | } 208 | 209 | err = CheckResponse(resp) 210 | if err != nil { 211 | // even though there was an error, we still return the response 212 | // in case the caller wants to inspect it further 213 | return response, err 214 | } 215 | 216 | if v != nil { 217 | if w, ok := v.(io.Writer); ok { 218 | io.Copy(w, resp.Body) 219 | } else { 220 | err = json.NewDecoder(resp.Body).Decode(v) 221 | if err == io.EOF { 222 | err = nil // ignore EOF errors caused by empty response body 223 | } 224 | } 225 | } 226 | 227 | return response, err 228 | } 229 | 230 | // addOptions adds the parameters in opt as URL query parameters to s. opt 231 | // must be a struct whose fields may contain "url" tags. 232 | func addOptions(s string, opt interface{}) (string, error) { 233 | v := reflect.ValueOf(opt) 234 | if v.Kind() == reflect.Ptr && v.IsNil() { 235 | return s, nil 236 | } 237 | 238 | u, err := url.Parse(s) 239 | if err != nil { 240 | return s, err 241 | } 242 | 243 | qs, err := query.Values(opt) 244 | if err != nil { 245 | return s, err 246 | } 247 | 248 | u.RawQuery = qs.Encode() 249 | return u.String(), nil 250 | } 251 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package adafruitio 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | const testUser = "test_username" 14 | 15 | var ( 16 | // mux is the HTTP request multiplexer used with the test server. 17 | mux *http.ServeMux 18 | 19 | // client is the Adafruit IO client being tested. 20 | client *Client 21 | 22 | // server is a test HTTP server used to provide mock API responses. 23 | server *httptest.Server 24 | ) 25 | 26 | // setup sets up a test HTTP server along with a github.Client that is 27 | // configured to talk to that test server. Tests should register handlers on 28 | // mux which provide mock responses for the API method being tested. 29 | func setup() { 30 | // test server 31 | mux = http.NewServeMux() 32 | server = httptest.NewServer(mux) 33 | 34 | // github client configured to use test server 35 | client = NewClient(testUser, "test-key") 36 | client.SetBaseURL(server.URL) 37 | } 38 | 39 | // teardown closes the test HTTP server. 40 | func teardown() { 41 | server.Close() 42 | } 43 | 44 | func serverPattern(pattern string) string { 45 | return fmt.Sprintf("%s/%s/%s", APIPath, testUser, pattern) 46 | } 47 | 48 | func testMethod(t *testing.T, r *http.Request, want string) { 49 | if got := r.Method; got != want { 50 | t.Errorf("Request method: %v, want %v", got, want) 51 | } 52 | } 53 | 54 | func testHeader(t *testing.T, r *http.Request, header string, want string) { 55 | if got := r.Header.Get(header); got != want { 56 | t.Errorf("Header.Get(%q) returned %s, want %s", header, got, want) 57 | } 58 | } 59 | 60 | func testQuery(t *testing.T, r *http.Request, field string, want string) { 61 | r.ParseForm() 62 | 63 | if len(r.Form[field]) < 1 { 64 | t.Errorf("expected Form[%q] to have a value", field) 65 | fmt.Printf("FORM: %q\n", r.Form) 66 | return 67 | } 68 | 69 | if got := r.Form[field][0]; got != want { 70 | t.Errorf("Form[%q] returned %s, want %s", field, got, want) 71 | } 72 | } 73 | 74 | func testBody(t *testing.T, r *http.Request, want string) { 75 | b, err := ioutil.ReadAll(r.Body) 76 | if err != nil { 77 | t.Errorf("Error reading request body: %v", err) 78 | } 79 | if got := string(b); got != want { 80 | t.Errorf("request Body is %s, want %s", got, want) 81 | } 82 | } 83 | 84 | func TestClientInitiation(t *testing.T) { 85 | assert := assert.New(t) 86 | 87 | c := NewClient("GIVEN USER", "GIVEN KEY") 88 | u, k := c.GetUserKey() 89 | 90 | assert.Equal("GIVEN USER", u, "expected to find GIVEN USER") 91 | assert.Equal("GIVEN KEY", k, "expected to find GIVEN KEY") 92 | } 93 | 94 | func TestClientAuthentication(t *testing.T) { 95 | setup() 96 | defer teardown() 97 | assert := assert.New(t) 98 | 99 | mux.HandleFunc("/", 100 | func(w http.ResponseWriter, r *http.Request) { 101 | testMethod(t, r, "GET") 102 | testHeader(t, r, "X-AIO-Key", "test-key") 103 | fmt.Fprintf(w, "ok") 104 | }, 105 | ) 106 | 107 | req, err := client.NewRequest("GET", "/", nil) 108 | assert.Nil(err) 109 | assert.NotNil(req) 110 | 111 | resp, err := client.Do(req, nil) 112 | assert.Nil(err) 113 | assert.NotNil(resp) 114 | } 115 | -------------------------------------------------------------------------------- /data_service.go: -------------------------------------------------------------------------------- 1 | package adafruitio 2 | 3 | import "fmt" 4 | 5 | // Data are the values contained by a Feed. 6 | type Data struct { 7 | ID string `json:"id,omitempty"` 8 | Value string `json:"value,omitempty"` 9 | FeedID int `json:"feed_id,omitempty"` 10 | FeedKey string `json:"feed_key,omitempty"` 11 | GroupID int `json:"group_id,omitempty"` 12 | Expiration string `json:"expiration,omitempty"` 13 | Latitude float64 `json:"lat,omitempty"` 14 | Longitude float64 `json:"lon,omitempty"` 15 | Elevation float64 `json:"ele,omitempty"` 16 | CompletedAt string `json:"completed_at,omitempty"` 17 | CreatedAt string `json:"created_at,omitempty"` 18 | UpdatedAt string `json:"updated_at,omitempty"` 19 | CreatedEpoch float64 `json:"created_epoch,omitempty"` 20 | } 21 | 22 | type DataFilter struct { 23 | StartTime string `url:"start_time,omitempty"` 24 | EndTime string `url:"end_time,omitempty"` 25 | } 26 | 27 | type DataService struct { 28 | client *Client 29 | } 30 | 31 | // All returns all Data for the currently selected Feed. See Client.SetFeed() 32 | // for details on selecting a Feed. 33 | func (s *DataService) All(opt *DataFilter) ([]*Data, *Response, error) { 34 | path, ferr := s.client.Feed.Path("/data") 35 | if ferr != nil { 36 | return nil, nil, ferr 37 | } 38 | 39 | path, oerr := addOptions(path, opt) 40 | if oerr != nil { 41 | return nil, nil, oerr 42 | } 43 | 44 | req, rerr := s.client.NewRequest("GET", path, nil) 45 | if rerr != nil { 46 | return nil, nil, rerr 47 | } 48 | 49 | // request populates Feed slice 50 | datas := make([]*Data, 0) 51 | resp, err := s.client.Do(req, &datas) 52 | if err != nil { 53 | return nil, resp, err 54 | } 55 | 56 | return datas, resp, nil 57 | } 58 | 59 | // Search has the same response format as All, but it accepts optional params 60 | // with which your data can be queried. 61 | func (s *DataService) Search(filter *DataFilter) ([]*Data, *Response, error) { 62 | path, ferr := s.client.Feed.Path("/data") 63 | if ferr != nil { 64 | return nil, nil, ferr 65 | } 66 | 67 | req, rerr := s.client.NewRequest("GET", path, nil) 68 | if rerr != nil { 69 | return nil, nil, rerr 70 | } 71 | 72 | // request populates Feed slice 73 | datas := make([]*Data, 0) 74 | resp, err := s.client.Do(req, &datas) 75 | if err != nil { 76 | return nil, resp, err 77 | } 78 | 79 | return datas, resp, nil 80 | } 81 | 82 | // Get returns a single Data element, identified by the given ID parameter. 83 | func (s *DataService) Get(id string) (*Data, *Response, error) { 84 | path, ferr := s.client.Feed.Path(fmt.Sprintf("/data/%s", id)) 85 | if ferr != nil { 86 | return nil, nil, ferr 87 | } 88 | 89 | req, rerr := s.client.NewRequest("GET", path, nil) 90 | if rerr != nil { 91 | return nil, nil, rerr 92 | } 93 | 94 | var data Data 95 | resp, err := s.client.Do(req, &data) 96 | if err != nil { 97 | return nil, resp, err 98 | } 99 | 100 | return &data, resp, nil 101 | } 102 | 103 | // Update takes an ID and a Data record, updates the record idendified by ID, 104 | // and returns a new, updated Data instance. 105 | func (s *DataService) Update(id string, data *Data) (*Data, *Response, error) { 106 | path, ferr := s.client.Feed.Path(fmt.Sprintf("/data/%s", id)) 107 | if ferr != nil { 108 | return nil, nil, ferr 109 | } 110 | 111 | req, rerr := s.client.NewRequest("PATCH", path, data) 112 | if rerr != nil { 113 | return nil, nil, rerr 114 | } 115 | 116 | var updatedData Data 117 | resp, err := s.client.Do(req, &updatedData) 118 | if err != nil { 119 | return nil, resp, err 120 | } 121 | 122 | return &updatedData, resp, nil 123 | } 124 | 125 | // Delete the Data identified by the given ID. 126 | func (s *DataService) Delete(id string) (*Response, error) { 127 | path, ferr := s.client.Feed.Path(fmt.Sprintf("/data/%s", id)) 128 | if ferr != nil { 129 | return nil, ferr 130 | } 131 | 132 | req, rerr := s.client.NewRequest("DELETE", path, nil) 133 | if rerr != nil { 134 | return nil, rerr 135 | } 136 | 137 | resp, err := s.client.Do(req, nil) 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | return resp, nil 143 | } 144 | 145 | // private method for handling the Next, Prev, and Last commands 146 | func (s *DataService) retrieve(command string) (*Data, *Response, error) { 147 | path, ferr := s.client.Feed.Path(fmt.Sprintf("/data/%v", command)) 148 | if ferr != nil { 149 | return nil, nil, ferr 150 | } 151 | 152 | req, rerr := s.client.NewRequest("GET", path, nil) 153 | if rerr != nil { 154 | return nil, nil, rerr 155 | } 156 | 157 | var data Data 158 | resp, err := s.client.Do(req, &data) 159 | if err != nil { 160 | return nil, resp, err 161 | } 162 | 163 | return &data, resp, nil 164 | } 165 | 166 | // Next returns the next Data in the stream. 167 | func (s *DataService) Next() (*Data, *Response, error) { 168 | return s.retrieve("next") 169 | } 170 | 171 | // Prev returns the previous Data in the stream. 172 | func (s *DataService) Prev() (*Data, *Response, error) { 173 | return s.retrieve("previous") 174 | } 175 | 176 | // First returns the first Data in the stream. 177 | func (s *DataService) First() (*Data, *Response, error) { 178 | return s.retrieve("first") 179 | } 180 | 181 | // Last returns the last Data in the stream. 182 | func (s *DataService) Last() (*Data, *Response, error) { 183 | return s.retrieve("last") 184 | } 185 | 186 | // Create adds a new Data value to an existing Feed. 187 | func (s *DataService) Create(dp *Data) (*Data, *Response, error) { 188 | path, ferr := s.client.Feed.Path("/data") 189 | if ferr != nil { 190 | return nil, nil, ferr 191 | } 192 | 193 | req, rerr := s.client.NewRequest("POST", path, dp) 194 | if rerr != nil { 195 | return nil, nil, rerr 196 | } 197 | 198 | // request populates a new datapoint 199 | point := &Data{} 200 | resp, err := s.client.Do(req, point) 201 | if err != nil { 202 | return nil, resp, err 203 | } 204 | 205 | return point, resp, nil 206 | } 207 | -------------------------------------------------------------------------------- /data_service_test.go: -------------------------------------------------------------------------------- 1 | package adafruitio 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestData_MissingFeed(t *testing.T) { 12 | setup() 13 | defer teardown() 14 | 15 | mux.HandleFunc(serverPattern("feeds/temperature/data"), 16 | func(w http.ResponseWriter, r *http.Request) { 17 | testMethod(t, r, "POST") 18 | fmt.Fprint(w, `{"id":"1", "value":"67.112"}`) 19 | }, 20 | ) 21 | 22 | assert := assert.New(t) 23 | 24 | dp := &Data{} 25 | datapoint, response, err := client.Data.Create(dp) 26 | 27 | assert.NotNil(err) 28 | assert.Nil(datapoint) 29 | assert.Nil(response) 30 | 31 | assert.Equal(err.Error(), "CurrentFeed must be set") 32 | } 33 | 34 | func TestData_Unauthenticated(t *testing.T) { 35 | setup() 36 | defer teardown() 37 | 38 | mux.HandleFunc(serverPattern("feeds/temperature/data"), 39 | func(w http.ResponseWriter, r *http.Request) { 40 | testMethod(t, r, "POST") 41 | fmt.Fprint(w, `{"id":"1", "value":"67.112"}`) 42 | }, 43 | ) 44 | 45 | assert := assert.New(t) 46 | 47 | dp := &Data{} 48 | datapoint, response, err := client.Data.Create(dp) 49 | 50 | assert.NotNil(err) 51 | assert.Nil(datapoint) 52 | assert.Nil(response) 53 | 54 | assert.Equal(err.Error(), "CurrentFeed must be set") 55 | } 56 | 57 | func TestDataCreate(t *testing.T) { 58 | setup() 59 | defer teardown() 60 | 61 | // prepare endpoint URL for just this request 62 | mux.HandleFunc(serverPattern("feeds/temperature/data"), 63 | func(w http.ResponseWriter, r *http.Request) { 64 | testMethod(t, r, "POST") 65 | fmt.Fprint(w, `{"id":"1", "value":"67.112"}`) 66 | }, 67 | ) 68 | 69 | assert := assert.New(t) 70 | 71 | client.SetFeed(&Feed{Key: "temperature"}) 72 | 73 | val := "67.112" 74 | 75 | dp := &Data{ 76 | Value: val, 77 | } 78 | datapoint, response, err := client.Data.Create(dp) 79 | 80 | assert.Nil(err) 81 | assert.NotNil(datapoint) 82 | assert.NotNil(response) 83 | 84 | assert.Equal("1", datapoint.ID) 85 | assert.Equal(val, datapoint.Value) 86 | } 87 | 88 | func TestDataGet(t *testing.T) { 89 | setup() 90 | defer teardown() 91 | 92 | // prepare endpoint URL for just this request 93 | mux.HandleFunc(serverPattern("feeds/temperature/data/1"), 94 | func(w http.ResponseWriter, r *http.Request) { 95 | testMethod(t, r, "GET") 96 | fmt.Fprint(w, `{"id":"1", "value":"67.112"}`) 97 | }, 98 | ) 99 | 100 | assert := assert.New(t) 101 | 102 | client.SetFeed(&Feed{Key: "temperature"}) 103 | 104 | datapoint, response, err := client.Data.Get("1") 105 | 106 | assert.Nil(err) 107 | assert.NotNil(datapoint) 108 | assert.NotNil(response) 109 | 110 | assert.Equal("1", datapoint.ID) 111 | assert.Equal("67.112", datapoint.Value) 112 | } 113 | 114 | func TestAllDataNoFilter(t *testing.T) { 115 | setup() 116 | defer teardown() 117 | 118 | // prepare endpoint URL for just this request 119 | mux.HandleFunc(serverPattern("feeds/temperature/data"), 120 | func(w http.ResponseWriter, r *http.Request) { 121 | testMethod(t, r, "GET") 122 | fmt.Fprint(w, `[{"id":"1", "value":"67.112"}]`) 123 | }, 124 | ) 125 | 126 | assert := assert.New(t) 127 | 128 | client.SetFeed(&Feed{Key: "temperature"}) 129 | 130 | // with no params 131 | datapoints, response, err := client.Data.All(nil) 132 | datapoint := datapoints[0] 133 | 134 | assert.Nil(err) 135 | assert.NotNil(datapoint) 136 | assert.NotNil(response) 137 | 138 | assert.Equal("1", datapoint.ID) 139 | assert.Equal("67.112", datapoint.Value) 140 | } 141 | 142 | func TestAllDataFilter(t *testing.T) { 143 | setup() 144 | defer teardown() 145 | 146 | // prepare endpoint URL for just this request 147 | mux.HandleFunc(serverPattern("feeds/temperature/data"), 148 | func(w http.ResponseWriter, r *http.Request) { 149 | testMethod(t, r, "GET") 150 | testQuery(t, r, "start_time", "2000-01-01") 151 | testQuery(t, r, "end_time", "2010-01-01") 152 | fmt.Fprint(w, `[{"id":"1", "value":"67.112"}]`) 153 | }, 154 | ) 155 | 156 | assert := assert.New(t) 157 | 158 | client.SetFeed(&Feed{Key: "temperature"}) 159 | 160 | // with no params 161 | datapoints, response, err := client.Data.All(&DataFilter{ 162 | StartTime: "2000-01-01", 163 | EndTime: "2010-01-01", 164 | }) 165 | datapoint := datapoints[0] 166 | 167 | assert.Nil(err) 168 | assert.NotNil(datapoint) 169 | assert.NotNil(response) 170 | 171 | assert.Equal("1", datapoint.ID) 172 | assert.Equal("67.112", datapoint.Value) 173 | } 174 | 175 | func TestDataDelete(t *testing.T) { 176 | setup() 177 | defer teardown() 178 | 179 | mux.HandleFunc(serverPattern("feeds/test/data/1"), 180 | func(w http.ResponseWriter, r *http.Request) { 181 | testMethod(t, r, "DELETE") 182 | }, 183 | ) 184 | 185 | assert := assert.New(t) 186 | 187 | client.SetFeed(&Feed{Key: "test"}) 188 | 189 | response, err := client.Data.Delete("1") 190 | 191 | assert.Nil(err) 192 | assert.NotNil(response) 193 | 194 | assert.Equal(200, response.StatusCode) 195 | } 196 | 197 | func TestDataQueue(t *testing.T) { 198 | setup() 199 | defer teardown() 200 | 201 | // prepare endpoint URL for just this request 202 | mux.HandleFunc(serverPattern("feeds/temperature/data/next"), 203 | func(w http.ResponseWriter, r *http.Request) { 204 | testMethod(t, r, "GET") 205 | fmt.Fprint(w, `{"id":"1", "value":"1"}`) 206 | }, 207 | ) 208 | 209 | mux.HandleFunc(serverPattern("feeds/temperature/data/previous"), 210 | func(w http.ResponseWriter, r *http.Request) { 211 | testMethod(t, r, "GET") 212 | fmt.Fprint(w, `{"id":"2", "value":"2"}`) 213 | }, 214 | ) 215 | 216 | mux.HandleFunc(serverPattern("feeds/temperature/data/last"), 217 | func(w http.ResponseWriter, r *http.Request) { 218 | testMethod(t, r, "GET") 219 | fmt.Fprint(w, `{"id":"3", "value":"3"}`) 220 | }, 221 | ) 222 | mux.HandleFunc(serverPattern("feeds/temperature/data/first"), 223 | func(w http.ResponseWriter, r *http.Request) { 224 | testMethod(t, r, "GET") 225 | fmt.Fprint(w, `{"id":"1", "value":"1"}`) 226 | }, 227 | ) 228 | assert := assert.New(t) 229 | 230 | client.SetFeed(&Feed{Key: "temperature"}) 231 | 232 | var ( 233 | datapoint *Data 234 | response *Response 235 | err error 236 | ) 237 | 238 | datapoint, response, err = client.Data.Next() 239 | assert.Nil(err) 240 | assert.NotNil(response) 241 | assert.Equal("1", datapoint.ID) 242 | assert.Equal("1", datapoint.Value) 243 | 244 | datapoint, response, err = client.Data.Prev() 245 | assert.Nil(err) 246 | assert.NotNil(response) 247 | assert.Equal("2", datapoint.ID) 248 | assert.Equal("2", datapoint.Value) 249 | 250 | datapoint, response, err = client.Data.Last() 251 | assert.Nil(err) 252 | assert.NotNil(response) 253 | assert.Equal("3", datapoint.ID) 254 | assert.Equal("3", datapoint.Value) 255 | 256 | datapoint, response, err = client.Data.First() 257 | assert.Nil(err) 258 | assert.NotNil(response) 259 | assert.Equal("1", datapoint.ID) 260 | assert.Equal("1", datapoint.Value) 261 | 262 | } 263 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Adafruit Author: Adam Bachman 2 | // This code is under the MIT License that can be found in the LICENSE file. 3 | 4 | /* 5 | The adafruitio package is a simple HTTP client for accessing v1 of the Adafruit IO REST API at https://io.adafruit.com. 6 | 7 | import "github.com/adafruit/io-client-go" 8 | 9 | Authentication for Adafruit IO is managed by providing your Adafruit IO token 10 | in the head of all web requests via the `X-AIO-Key` header. This is handled for 11 | you by the client library, which expects you API Token when it is initialized. 12 | 13 | We recommend keeping the Token in an environment variable to avoid including it 14 | directly in your code. 15 | 16 | client := adafruitio.NewClient(os.Getenv("ADAFRUIT_IO_KEY")) 17 | feeds, _, err := adafruitio.Feed.All() 18 | 19 | Some API calls expect parameters, which must be provided when making the call. 20 | 21 | feed := &aio.Feed{Name: "my-new-feed"} 22 | client.Feed.Create(newFeed) 23 | 24 | Data related API calls expect a Feed to be set before the call is made. 25 | 26 | **NOTE:** the Feed doesn't have to exist yet if you're using the `Data.Send()` 27 | method, but it still needs to be set. If you're relying on the Data API to 28 | create the Feed, make sure you set the `Key` attribute on the new Feed. 29 | 30 | feed := &aio.Feed{Name: "My New Feed", Key: "my-new-feed"} 31 | client.SetFeed(newFeed) 32 | client.Data.Send(&adafruitio.Data{Value: 100}) 33 | 34 | You can see the v1 Adafruit IO REST API documentation online at https://io.adafruit.com/api/docs/ 35 | */ 36 | package adafruitio 37 | -------------------------------------------------------------------------------- /docs/TODO: -------------------------------------------------------------------------------- 1 | API v2 Endpoints 2 | 3 | - [x] Feeds 4 | - [x] Index 5 | - [x] Create 6 | - [x] Read 7 | - [x] Update 8 | - [x] Delete 9 | - [x] Data 10 | - [x] Index 11 | - [x] Create 12 | - [x] Read 13 | - [x] Update 14 | - [x] Delete 15 | - [x] Helper Methods 16 | - [x] First 17 | - [x] Next 18 | - [x] Last 19 | - [x] Previous 20 | - [] Groups 21 | - [x] Index 22 | - [x] Create 23 | - [X] Read 24 | - [X] Update 25 | - [X] Delete 26 | -------------------------------------------------------------------------------- /example_interface_test.go: -------------------------------------------------------------------------------- 1 | package adafruitio_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | 8 | adafruitio "github.com/adafruit/io-client-go/v2" 9 | ) 10 | 11 | var ( 12 | key string 13 | feeds []*adafruitio.Feed 14 | ) 15 | 16 | func Example() { 17 | // Load ADAFRUIT_IO_KEY from environment 18 | client := adafruitio.NewClient(os.Getenv("ADAFRUIT_IO_USERNAME"), os.Getenv("ADAFRUIT_IO_KEY")) 19 | 20 | // set custom API URL 21 | client.SetBaseURL("http://localhost:3002") 22 | 23 | // Get the list of all available feeds 24 | feeds, _, err := client.Feed.All() 25 | if err != nil { 26 | fmt.Println("UNEXPECTED ERROR!", err) 27 | panic(err) 28 | } 29 | 30 | // View the resulting feed list 31 | for _, feed := range feeds { 32 | jsonBytes, _ := json.MarshalIndent(feed, "", " ") 33 | fmt.Printf("[%v]\n", feed.Name) 34 | fmt.Println(string(jsonBytes)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/data/all_actions.go: -------------------------------------------------------------------------------- 1 | // Demo showing Data listing, creation, updating, and deletion. 2 | package main 3 | 4 | import ( 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "math/rand" 9 | "os" 10 | "time" 11 | 12 | adafruitio "github.com/adafruit/io-client-go/v2" 13 | ) 14 | 15 | var ( 16 | useURL string 17 | username string 18 | key string 19 | feedName string 20 | value string 21 | ) 22 | 23 | func prepare() { 24 | rand.Seed(time.Now().UnixNano()) 25 | 26 | flag.StringVar(&useURL, "url", "", "Adafruit IO URL") 27 | flag.StringVar(&username, "user", "", "your Adafruit IO user name") 28 | flag.StringVar(&key, "key", "", "your Adafruit IO key") 29 | flag.StringVar(&feedName, "feed", "", "the key of the feed to manipulate") 30 | flag.StringVar(&value, "value", rval(), "the value to send") 31 | 32 | if useURL == "" { 33 | // no arg given, try ENV 34 | useURL = os.Getenv("ADAFRUIT_IO_URL") 35 | } 36 | 37 | if key == "" { 38 | key = os.Getenv("ADAFRUIT_IO_KEY") 39 | } 40 | 41 | if username == "" { 42 | username = os.Getenv("ADAFRUIT_IO_USERNAME") 43 | } 44 | 45 | flag.Parse() 46 | 47 | if feedName == "" { 48 | panic("A feed name must be specified") 49 | } 50 | 51 | } 52 | 53 | func rval() string { 54 | return fmt.Sprintf("%f", rand.Float32()*100.0) 55 | } 56 | 57 | func render(label string, d *adafruitio.Data) { 58 | dbytes, _ := json.MarshalIndent(d, "", " ") 59 | fmt.Printf("--- %v\n", label) 60 | fmt.Println(string(dbytes)) 61 | } 62 | 63 | func title(label string) { 64 | fmt.Printf("\n\n%v\n\n", label) 65 | } 66 | 67 | func main() { 68 | prepare() 69 | 70 | client := adafruitio.NewClient(username, key) 71 | if useURL != "" { 72 | client.SetBaseURL(useURL) 73 | } 74 | feed, _, ferr := client.Feed.Get(feedName) 75 | if ferr != nil { 76 | panic(ferr) 77 | } 78 | 79 | // create a data point on an existing Feed 80 | client.SetFeed(feed) 81 | val := &adafruitio.Data{Value: value, FeedKey: feedName} 82 | 83 | title("Create and Check") 84 | 85 | dp, _, err := client.Data.Create(val) 86 | if err != nil { 87 | fmt.Println("unable to create data") 88 | panic(err) 89 | } 90 | render("new point", dp) 91 | 92 | ndp, _, err := client.Data.Get(dp.ID) 93 | if err != nil { 94 | fmt.Println("unable to retrieve data") 95 | panic(err) 96 | } 97 | render("found point", ndp) 98 | 99 | // update point 100 | client.Data.Update(dp.ID, &adafruitio.Data{Value: rval()}) 101 | 102 | // reload 103 | ndp, _, err = client.Data.Get(dp.ID) 104 | if err != nil { 105 | fmt.Println("unable to retrieve data") 106 | panic(err) 107 | } 108 | render("updated point", ndp) 109 | time.Sleep(2 * time.Second) 110 | 111 | // Generate some more Data to fill out the stream 112 | for i := 0; i < 4; i += 1 { 113 | client.Data.Create(&adafruitio.Data{Value: rval()}) 114 | } 115 | 116 | // Display all Data in the stream 117 | title("All Data") 118 | dts, _, err := client.Data.All(nil) 119 | if err != nil { 120 | fmt.Println("unable to retrieve data") 121 | panic(err) 122 | } 123 | for _, data := range dts { 124 | render(fmt.Sprintf("ID <%v>", data.ID), data) 125 | } 126 | time.Sleep(2 * time.Second) 127 | 128 | // stream commands: Last, Prev, and Next 129 | title("Queue related commands") 130 | 131 | ndp, _, err = client.Data.Last() 132 | if err != nil { 133 | fmt.Println("unable to retrieve data") 134 | panic(err) 135 | } 136 | render("last point", ndp) 137 | 138 | ndp, _, err = client.Data.Prev() 139 | if err != nil { 140 | fmt.Println("unable to retrieve data") 141 | panic(err) 142 | } 143 | render("prev point", ndp) 144 | 145 | ndp, _, err = client.Data.First() 146 | if err != nil { 147 | fmt.Println("unable to retrieve data") 148 | panic(err) 149 | } 150 | render("first point", ndp) 151 | 152 | ndp, _, err = client.Data.Next() 153 | if err != nil { 154 | fmt.Println("unable to retrieve data") 155 | panic(err) 156 | } 157 | render("next point", ndp) 158 | 159 | // delete 160 | title("Delete") 161 | _, derr := client.Data.Delete(ndp.ID) 162 | if derr == nil { 163 | fmt.Println("ok") 164 | } else { 165 | fmt.Println("failed to delete!") 166 | } 167 | time.Sleep(1 * time.Second) 168 | 169 | title("All Data (updated)") 170 | dts, _, err = client.Data.All(nil) 171 | if err != nil { 172 | fmt.Println("unable to retrieve data") 173 | panic(err) 174 | } 175 | for _, data := range dts { 176 | render(fmt.Sprintf("ID <%v>", data.ID), data) 177 | } 178 | time.Sleep(2 * time.Second) 179 | 180 | // Now, generate a single point and do a filtered search for it 181 | t := time.Now().Unix() // get current time 182 | time.Sleep(2 * time.Second) 183 | client.Data.Create(&adafruitio.Data{Value: rval()}) // create point 2 seconds later 184 | 185 | title(fmt.Sprintf("Filtered Data, since %v", t)) 186 | dts, _, err = client.Data.All(&adafruitio.DataFilter{StartTime: fmt.Sprintf("%d", t)}) 187 | if err != nil { 188 | fmt.Println("unable to retrieve data") 189 | panic(err) 190 | } 191 | for _, data := range dts { 192 | render(fmt.Sprintf("ID <%v>", data.ID), data) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /examples/debug/request_viewer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | A sanity-check sketch for inspecting the raw request before it's sent to 5 | Adafruit IO. To use, change the API calls inside the `CallAPI` method to the 6 | one / ones you want to see. 7 | 8 | No requests generated by this sketch will be sent to io.adafruit.com, so it's 9 | safe to use a bogus secret key. 10 | 11 | For example: 12 | 13 | $ go run examples/debug/request_viewer.go -user "username" -key "12345ABC" 14 | 2016/05/26 09:10:07 -- received request -- 15 | --- 16 | POST /api/v2/username/feeds/beta-test/data HTTP/1.1 17 | Host: 127.0.0.1:53626 18 | Accept: application/json 19 | Accept-Encoding: gzip 20 | Content-Type: application/json 21 | User-Agent: Adafruit IO Go Client v0.1 22 | X-Aio-Key: 12345ABC 23 | 24 | {"value":22} 25 | --- 26 | 27 | */ 28 | 29 | import ( 30 | "flag" 31 | "fmt" 32 | "log" 33 | "net/http" 34 | "net/http/httptest" 35 | "net/http/httputil" 36 | "os" 37 | 38 | adafruitio "github.com/adafruit/io-client-go/v2" 39 | ) 40 | 41 | // Add the API call you want to examine here to see it output at the command line. 42 | func CallAPI(client *adafruitio.Client) { 43 | client.SetFeed(&adafruitio.Feed{Key: "beta-test"}) 44 | client.Data.Create(&adafruitio.Data{Value: "22"}) 45 | } 46 | 47 | func main() { 48 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 49 | dump, err := httputil.DumpRequest(r, true) 50 | if err != nil { 51 | http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 52 | return 53 | } 54 | log.Println("received request") 55 | fmt.Println("---") 56 | fmt.Printf("%v", string(dump)) 57 | fmt.Println("---") 58 | fmt.Fprint(w, "{}") 59 | })) 60 | defer ts.Close() 61 | 62 | var key string 63 | var username string 64 | flag.StringVar(&key, "key", "", "your Adafruit IO key") 65 | flag.StringVar(&username, "user", "", "your Adafruit IO user name") 66 | flag.Parse() 67 | 68 | if key == "" { 69 | key = os.Getenv("ADAFRUIT_IO_KEY") 70 | } 71 | 72 | if username == "" { 73 | username = os.Getenv("ADAFRUIT_IO_USERNAME") 74 | } 75 | 76 | client := adafruitio.NewClient(username, key) 77 | 78 | if ts.URL != "" { 79 | client.SetBaseURL(ts.URL) 80 | } 81 | 82 | CallAPI(client) 83 | } 84 | -------------------------------------------------------------------------------- /examples/feed_browser/server.go: -------------------------------------------------------------------------------- 1 | // A simple web service that displays data from your Adafruit IO feeds. 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | "os" 11 | "regexp" 12 | 13 | adafruitio "github.com/adafruit/io-client-go/v2" 14 | ) 15 | 16 | var ( 17 | aioURL string 18 | aioUsername string 19 | aioKey string 20 | feedMatcher = regexp.MustCompile(`/feed/([a-z0-9-]+)`) 21 | head = ` 22 | 23 | 30 |
` 31 | tail = `
` 32 | ) 33 | 34 | // Get command line flags, fallback to environment variables 35 | func prepare() { 36 | flag.StringVar(&aioURL, "url", "", "Adafruit IO URL") 37 | flag.StringVar(&aioUsername, "user", "", "your Adafruit IO user name") 38 | flag.StringVar(&aioKey, "key", "", "your Adafruit IO key") 39 | 40 | if aioURL == "" { 41 | // no arg given, try ENV 42 | aioURL = os.Getenv("ADAFRUIT_IO_URL") 43 | } 44 | 45 | if aioKey == "" { 46 | aioKey = os.Getenv("ADAFRUIT_IO_KEY") 47 | } 48 | 49 | if aioUsername == "" { 50 | aioUsername = os.Getenv("ADAFRUIT_IO_USERNAME") 51 | } 52 | 53 | flag.Parse() 54 | } 55 | 56 | func main() { 57 | prepare() 58 | 59 | // setup AIO client 60 | client := adafruitio.NewClient(aioUsername, aioKey) 61 | 62 | // setup server 63 | mux := http.NewServeMux() 64 | 65 | // GET / 66 | // get feeds list 67 | mux.HandleFunc("/", 68 | func(w http.ResponseWriter, r *http.Request) { 69 | fmt.Fprintf(w, head) 70 | defer fmt.Fprintf(w, tail) 71 | 72 | fmt.Fprint(w, "

Feeds

") 73 | 74 | feeds, _, err := client.Feed.All() 75 | if err != nil { 76 | fmt.Fprintf(w, "ERROR finding feed. %v", err.Error()) 77 | return 78 | } 79 | 80 | fmt.Fprint(w, "
    ") 81 | for _, feed := range feeds { 82 | fmt.Fprintf(w, 83 | `
  • %v
  • `, 84 | feed.Key, 85 | feed.Name, 86 | ) 87 | } 88 | fmt.Fprint(w, "
") 89 | }, 90 | ) 91 | 92 | // GET /feed/{feedname} 93 | // get data for a given feed 94 | mux.HandleFunc("/feed/", 95 | func(w http.ResponseWriter, r *http.Request) { 96 | fmt.Fprintf(w, head) 97 | defer fmt.Fprintf(w, tail) 98 | 99 | var parts []string 100 | var feedName string 101 | 102 | parts = feedMatcher.FindStringSubmatch(r.URL.Path) 103 | if len(parts) > 0 { 104 | feedName = parts[1] 105 | fmt.Fprintf(w, "

Feed: %v

back", feedName) 106 | } else { 107 | fmt.Fprint(w, "ERROR: unable to find feed") 108 | return 109 | } 110 | 111 | feed, _, err := client.Feed.Get(feedName) 112 | 113 | if err != nil { 114 | fmt.Fprintf(w, "ERROR finding feed. %v", err.Error()) 115 | return 116 | } 117 | 118 | // set client Feed 119 | client.SetFeed(feed) 120 | 121 | // get all Data for the given Feed 122 | data, _, err := client.Data.All(nil) 123 | if err != nil { 124 | fmt.Fprintf(w, "ERROR loading data. %v", err.Error()) 125 | return 126 | } 127 | 128 | // render data in a table 129 | fmt.Fprint(w, "") 130 | for _, d := range data { 131 | fmt.Fprintf(w, 132 | ` 133 | 134 | 135 | `, 136 | d.CreatedAt, 137 | d.Value, 138 | ) 139 | } 140 | fmt.Fprint(w, "
Created AtValue
%v%v
") 141 | }, 142 | ) 143 | 144 | // start server 145 | log.Println("Listening on port 3009...") 146 | http.ListenAndServe(":3009", mux) 147 | } 148 | -------------------------------------------------------------------------------- /examples/feeds/all_actions.go: -------------------------------------------------------------------------------- 1 | // Demo showing feed listing, creation, updating, and deletion. 2 | package main 3 | 4 | import ( 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "os" 9 | "time" 10 | 11 | adafruitio "github.com/adafruit/io-client-go/v2" 12 | ) 13 | 14 | var ( 15 | useURL string 16 | username string 17 | key string 18 | ) 19 | 20 | func prepare() { 21 | flag.StringVar(&useURL, "url", "", "Adafruit IO URL") 22 | flag.StringVar(&username, "user", "", "your Adafruit IO user name") 23 | flag.StringVar(&key, "key", "", "your Adafruit IO key") 24 | 25 | if useURL == "" { 26 | // no arg given, try ENV 27 | useURL = os.Getenv("ADAFRUIT_IO_URL") 28 | } 29 | 30 | if key == "" { 31 | key = os.Getenv("ADAFRUIT_IO_KEY") 32 | } 33 | 34 | if username == "" { 35 | username = os.Getenv("ADAFRUIT_IO_USERNAME") 36 | } 37 | 38 | flag.Parse() 39 | } 40 | 41 | func render(label string, f *adafruitio.Feed) { 42 | sfeed, _ := json.MarshalIndent(f, "", " ") 43 | fmt.Printf("--- %v\n", label) 44 | fmt.Println(string(sfeed)) 45 | } 46 | 47 | func title(label string) { 48 | fmt.Printf("\n\n%v\n\n", label) 49 | } 50 | 51 | func ShowAll(client *adafruitio.Client) { 52 | // Get the list of all available feeds 53 | feeds, _, err := client.Feed.All() 54 | if err != nil { 55 | fmt.Println("UNEXPECTED ERROR!", err) 56 | panic(err) 57 | } 58 | 59 | for _, feed := range feeds { 60 | render(feed.Name, feed) 61 | } 62 | } 63 | 64 | func main() { 65 | prepare() 66 | 67 | client := adafruitio.NewClient(username, key) 68 | if useURL != "" { 69 | client.SetBaseURL(useURL) 70 | } 71 | 72 | title("All") 73 | 74 | ShowAll(client) 75 | time.Sleep(1 * time.Second) 76 | 77 | title("Create") 78 | 79 | newFeed := &adafruitio.Feed{Name: "my-new-feed", Description: "an example of creating feeds"} 80 | client.Feed.Create(newFeed) 81 | render("NEW FEED", newFeed) 82 | time.Sleep(1 * time.Second) 83 | 84 | if newFeed.ID == 0 { 85 | panic(fmt.Errorf("could not create feed")) 86 | } 87 | 88 | title("All") 89 | 90 | ShowAll(client) 91 | time.Sleep(1 * time.Second) 92 | 93 | title("Update") 94 | 95 | updatedFeed, _, _ := client.Feed.Update(newFeed.Key, &adafruitio.Feed{Name: "renamed-new-feed"}) 96 | render("UPDATED FEED", updatedFeed) 97 | time.Sleep(1 * time.Second) 98 | 99 | title("All") 100 | 101 | ShowAll(client) 102 | time.Sleep(1 * time.Second) 103 | 104 | title("Just the new one") 105 | 106 | feed, _, _ := client.Feed.Get(updatedFeed.Key) 107 | render("SHOW", feed) 108 | time.Sleep(1 * time.Second) 109 | 110 | title("Delete") 111 | 112 | _, err := client.Feed.Delete(newFeed.Key) 113 | if err == nil { 114 | fmt.Println("ok") 115 | } 116 | time.Sleep(1 * time.Second) 117 | 118 | title("All") 119 | 120 | ShowAll(client) 121 | time.Sleep(1 * time.Second) 122 | } 123 | -------------------------------------------------------------------------------- /examples/groups/all_actions.go: -------------------------------------------------------------------------------- 1 | // Demo showing Group listing, creation, updating, and deletion. 2 | package main 3 | 4 | import ( 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "math/rand" 9 | "os" 10 | "time" 11 | 12 | adafruitio "github.com/adafruit/io-client-go/v2" 13 | ) 14 | 15 | var ( 16 | useURL string 17 | username string 18 | key string 19 | ) 20 | 21 | func prepare() { 22 | rand.Seed(time.Now().UnixNano()) 23 | 24 | flag.StringVar(&useURL, "url", "", "Adafruit IO URL. Defaults to https://io.adafruit.com.") 25 | flag.StringVar(&username, "user", "", "your Adafruit IO user name") 26 | flag.StringVar(&key, "key", "", "your Adafruit IO key") 27 | 28 | if useURL == "" { 29 | // no arg given, try ENV 30 | useURL = os.Getenv("ADAFRUIT_IO_URL") 31 | } 32 | 33 | if key == "" { 34 | key = os.Getenv("ADAFRUIT_IO_KEY") 35 | } 36 | 37 | if username == "" { 38 | username = os.Getenv("ADAFRUIT_IO_USERNAME") 39 | } 40 | 41 | flag.Parse() 42 | } 43 | 44 | func render(label string, f *adafruitio.Group) { 45 | sfeed, _ := json.MarshalIndent(f, "", " ") 46 | fmt.Printf("--- %v\n", label) 47 | fmt.Println(string(sfeed)) 48 | } 49 | 50 | func title(label string) { 51 | fmt.Printf("\n\n%v\n\n", label) 52 | } 53 | 54 | func ShowAll(client *adafruitio.Client) { 55 | title("All") 56 | groups, _, err := client.Group.All() 57 | if err != nil { 58 | panic(err) 59 | } 60 | for _, g := range groups { 61 | render(g.Name, g) 62 | } 63 | } 64 | 65 | func pause() { 66 | time.Sleep(2 * time.Second) 67 | } 68 | 69 | func main() { 70 | prepare() 71 | 72 | client := adafruitio.NewClient(username, key) 73 | if useURL != "" { 74 | client.SetBaseURL(useURL) 75 | } 76 | 77 | ShowAll(client) 78 | pause() 79 | 80 | // CREATE 81 | name := fmt.Sprintf("a_new_group_%d", rand.Int()) 82 | fmt.Printf("CREATING %v\n", name) 83 | g, resp, err := client.Group.Create(&adafruitio.Group{Name: name}) 84 | if err != nil { 85 | // resp.Debug() 86 | fmt.Printf("failed to create group") 87 | panic(err) 88 | } else if resp.StatusCode > 299 { 89 | fmt.Printf("Unexpected status: %v", resp.Status) 90 | panic(fmt.Errorf("failed to create group")) 91 | } else { 92 | fmt.Println("ok") 93 | } 94 | pause() 95 | 96 | // GET 97 | newg, _, err := client.Group.Get(g.Key) 98 | if err != nil { 99 | panic(err) 100 | } 101 | render("new group", newg) 102 | pause() 103 | 104 | // UPDATE (only Name and Description can be modified) 105 | g.Name = fmt.Sprintf("name_changed_to_%d", rand.Int()) 106 | g.Description = "Now this group has a description." 107 | 108 | fmt.Printf("changing name to %v\n", g.Name) 109 | newg, _, err = client.Group.Update(g.Key, g) 110 | if err != nil { 111 | panic(err) 112 | } 113 | render("updated group", newg) 114 | pause() 115 | 116 | // DELETE 117 | time.Sleep(2 * time.Second) 118 | title("deleting group") 119 | _, err = client.Group.Delete(newg.Key) 120 | if err == nil { 121 | fmt.Println("ok") 122 | } 123 | pause() 124 | 125 | // SHOW ALL 126 | ShowAll(client) 127 | } 128 | -------------------------------------------------------------------------------- /feed_service.go: -------------------------------------------------------------------------------- 1 | package adafruitio 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | ) 7 | 8 | type FeedService struct { 9 | // CurrentFeed is the Feed used for all Data access. 10 | CurrentFeed *Feed 11 | 12 | client *Client 13 | } 14 | 15 | // Path generates a Feed-specific path with the given suffix. 16 | func (s *FeedService) Path(suffix string) (string, error) { 17 | ferr := s.client.checkFeed() 18 | if ferr != nil { 19 | return "", ferr 20 | } 21 | return path.Join(fmt.Sprintf("feeds/%v", s.CurrentFeed.Key), suffix), nil 22 | } 23 | 24 | type Owner struct { 25 | ID int `json:"id,omitempty"` 26 | Username string `json:"username,omitempty"` 27 | } 28 | 29 | type Feed struct { 30 | ID int `json:"id,omitempty"` 31 | Name string `json:"name,omitempty"` 32 | Key string `json:"key,omitempty"` 33 | Username string `json:"username,omitempty"` 34 | Owner *Owner `json:"owner,omitempty"` 35 | Description string `json:"description,omitempty"` 36 | UnitType string `json:"unit_type,omitempty"` 37 | UnitSymbol string `json:"unit_symbol,omitempty"` 38 | History bool `json:"history,omitempty"` 39 | Visibility string `json:"visibility,omitempty"` 40 | License string `json:"license,omitempty"` 41 | Enabled bool `json:"enabled,omitempty"` 42 | LastValue string `json:"last_value,omitempty"` 43 | Status string `json:"status,omitempty"` 44 | StatusNotify bool `json:"status_notify,omitempty"` 45 | StatusTimeout int `json:"status_timeout,omitempty"` 46 | Shared bool `json:"is_shared,omitempty"` 47 | CreatedAt string `json:"created_at,omitempty"` 48 | UpdatedAt string `json:"updated_at,omitempty"` 49 | } 50 | 51 | // All lists all available feeds. 52 | func (s *FeedService) All() ([]*Feed, *Response, error) { 53 | path := "feeds" 54 | 55 | req, rerr := s.client.NewRequest("GET", path, nil) 56 | if rerr != nil { 57 | return nil, nil, rerr 58 | } 59 | 60 | // request populates Feed slice 61 | feeds := make([]*Feed, 0) 62 | resp, err := s.client.Do(req, &feeds) 63 | if err != nil { 64 | return nil, resp, err 65 | } 66 | 67 | return feeds, resp, nil 68 | } 69 | 70 | // Get returns the Feed record identified by the given parameter. Parameter can 71 | // be the Feed's Name, Key, or ID. 72 | func (s *FeedService) Get(key string) (*Feed, *Response, error) { 73 | path := fmt.Sprintf("feeds/%s", key) 74 | 75 | req, rerr := s.client.NewRequest("GET", path, nil) 76 | if rerr != nil { 77 | return nil, nil, rerr 78 | } 79 | 80 | var feed Feed 81 | resp, err := s.client.Do(req, &feed) 82 | if err != nil { 83 | return nil, resp, err 84 | } 85 | 86 | return &feed, resp, nil 87 | } 88 | 89 | // Create takes a Feed record, creates it, and returns the updated record or an error. 90 | func (s *FeedService) Create(feed *Feed) (*Feed, *Response, error) { 91 | path := "feeds" 92 | 93 | req, rerr := s.client.NewRequest("POST", path, feed) 94 | if rerr != nil { 95 | return nil, nil, rerr 96 | } 97 | 98 | resp, err := s.client.Do(req, feed) 99 | if err != nil { 100 | return nil, resp, err 101 | } 102 | 103 | return feed, resp, nil 104 | } 105 | 106 | // Update takes an ID and a Feed record, updates it, and returns an updated 107 | // record instance or an error. 108 | // 109 | // Only the Feed Name and Description can be modified. 110 | func (s *FeedService) Update(key string, feed *Feed) (*Feed, *Response, error) { 111 | path := fmt.Sprintf("feeds/%s", key) 112 | 113 | req, rerr := s.client.NewRequest("PATCH", path, feed) 114 | if rerr != nil { 115 | return nil, nil, rerr 116 | } 117 | 118 | var updatedFeed Feed 119 | resp, err := s.client.Do(req, &updatedFeed) 120 | if err != nil { 121 | return nil, resp, err 122 | } 123 | 124 | return &updatedFeed, resp, nil 125 | } 126 | 127 | // Delete the Feed identified by the given ID. 128 | func (s *FeedService) Delete(key string) (*Response, error) { 129 | path := fmt.Sprintf("feeds/%s", key) 130 | 131 | req, rerr := s.client.NewRequest("DELETE", path, nil) 132 | if rerr != nil { 133 | return nil, rerr 134 | } 135 | 136 | resp, err := s.client.Do(req, nil) 137 | if err != nil { 138 | return nil, err 139 | } 140 | 141 | return resp, nil 142 | } 143 | -------------------------------------------------------------------------------- /feed_service_test.go: -------------------------------------------------------------------------------- 1 | package adafruitio 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestFeedAll(t *testing.T) { 12 | setup() 13 | defer teardown() 14 | 15 | mux.HandleFunc(serverPattern("feeds"), 16 | func(w http.ResponseWriter, r *http.Request) { 17 | testMethod(t, r, "GET") 18 | fmt.Fprint(w, `[{"id":1, "name":"feed"}]`) 19 | }, 20 | ) 21 | 22 | assert := assert.New(t) 23 | 24 | feeds, response, err := client.Feed.All() 25 | 26 | assert.Nil(err) 27 | assert.NotNil(feeds) 28 | assert.NotNil(response) 29 | 30 | feed := feeds[0] 31 | 32 | assert.Equal(1, feed.ID) 33 | assert.Equal("feed", feed.Name) 34 | } 35 | 36 | func TestFeedGet(t *testing.T) { 37 | setup() 38 | defer teardown() 39 | 40 | mux.HandleFunc(serverPattern("feeds/test"), 41 | func(w http.ResponseWriter, r *http.Request) { 42 | testMethod(t, r, "GET") 43 | fmt.Fprint(w, `{"id":1, "name":"test"}`) 44 | }, 45 | ) 46 | 47 | assert := assert.New(t) 48 | 49 | feed, response, err := client.Feed.Get("test") 50 | 51 | assert.Nil(err) 52 | assert.NotNil(feed) 53 | assert.NotNil(response) 54 | 55 | assert.Equal(1, feed.ID) 56 | assert.Equal("test", feed.Name) 57 | } 58 | 59 | func TestFeedCreate(t *testing.T) { 60 | setup() 61 | defer teardown() 62 | 63 | mux.HandleFunc(serverPattern("feeds"), 64 | func(w http.ResponseWriter, r *http.Request) { 65 | testMethod(t, r, "POST") 66 | fmt.Fprint(w, `{"id":1, "name":"test"}`) 67 | }, 68 | ) 69 | 70 | assert := assert.New(t) 71 | 72 | nfeed := &Feed{Name: "test"} 73 | 74 | feed, response, err := client.Feed.Create(nfeed) 75 | 76 | assert.Nil(err) 77 | assert.NotNil(feed) 78 | assert.NotNil(response) 79 | 80 | assert.Equal(1, feed.ID) 81 | assert.Equal("test", feed.Name) 82 | } 83 | 84 | func TestFeedUpdate(t *testing.T) { 85 | setup() 86 | defer teardown() 87 | 88 | mux.HandleFunc(serverPattern("feeds/test"), 89 | func(w http.ResponseWriter, r *http.Request) { 90 | testMethod(t, r, "PATCH") 91 | fmt.Fprint(w, `{"id":1, "name":"test-2"}`) 92 | }, 93 | ) 94 | 95 | assert := assert.New(t) 96 | 97 | feed := &Feed{ID: 1, Name: "test"} 98 | 99 | ufeed, response, err := client.Feed.Update("test", feed) 100 | 101 | assert.Nil(err) 102 | assert.NotNil(ufeed) 103 | assert.NotNil(response) 104 | 105 | assert.Equal(1, ufeed.ID) 106 | assert.Equal("test-2", ufeed.Name) 107 | assert.NotEqual(&feed, &ufeed) 108 | } 109 | 110 | func TestFeedDelete(t *testing.T) { 111 | setup() 112 | defer teardown() 113 | 114 | mux.HandleFunc(serverPattern("feeds/test"), 115 | func(w http.ResponseWriter, r *http.Request) { 116 | testMethod(t, r, "DELETE") 117 | }, 118 | ) 119 | 120 | assert := assert.New(t) 121 | 122 | response, err := client.Feed.Delete("test") 123 | 124 | assert.Nil(err) 125 | assert.NotNil(response) 126 | 127 | assert.Equal(200, response.StatusCode) 128 | } 129 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/adafruit/io-client-go/v2 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/google/go-querystring v1.1.0 7 | github.com/stretchr/testify v1.7.1 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.0 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.0 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= 4 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 5 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 6 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 10 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 11 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 12 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 15 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 16 | gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= 17 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 18 | -------------------------------------------------------------------------------- /group_service.go: -------------------------------------------------------------------------------- 1 | // GroupService provides CRUD access to Groups. 2 | 3 | package adafruitio 4 | 5 | import "fmt" 6 | 7 | type Group struct { 8 | ID int `json:"id,omitempty"` 9 | Name string `json:"name,omitempty"` 10 | Key string `json:"key,omitempty"` 11 | Owner *Owner `json:"owner,omitempty"` 12 | UserID int `json:"user_id,omitempty"` 13 | Description string `json:"description,omitempty"` 14 | CreatedAt string `json:"created_at,omitempty"` 15 | UpdatedAt string `json:"updated_at,omitempty"` 16 | Feeds []*Feed `json:"feeds,omitempty"` 17 | Visibility string `json:"visibility"` 18 | Shared bool `json:"is_shared,omitempty"` 19 | } 20 | 21 | type GroupService struct { 22 | client *Client 23 | } 24 | 25 | // All returns all Groups for the current account. 26 | func (s *GroupService) All() ([]*Group, *Response, error) { 27 | path := "groups" 28 | 29 | req, rerr := s.client.NewRequest("GET", path, nil) 30 | if rerr != nil { 31 | return nil, nil, rerr 32 | } 33 | 34 | // request populates Feed slice 35 | groups := make([]*Group, 0) 36 | resp, err := s.client.Do(req, &groups) 37 | if err != nil { 38 | return nil, resp, err 39 | } 40 | 41 | return groups, resp, nil 42 | } 43 | 44 | // Create makes a new Group and either returns a new Group instance or an error. 45 | func (s *GroupService) Create(g *Group) (*Group, *Response, error) { 46 | path := "groups" 47 | 48 | req, rerr := s.client.NewRequest("POST", path, g) 49 | if rerr != nil { 50 | return nil, nil, rerr 51 | } 52 | 53 | var group Group 54 | resp, err := s.client.Do(req, &group) 55 | if err != nil { 56 | return nil, resp, err 57 | } 58 | 59 | return &group, resp, nil 60 | } 61 | 62 | // Get returns the Group record identified by the given ID 63 | func (s *GroupService) Get(key string) (*Group, *Response, error) { 64 | path := fmt.Sprintf("groups/%s", key) 65 | 66 | req, rerr := s.client.NewRequest("GET", path, nil) 67 | if rerr != nil { 68 | return nil, nil, rerr 69 | } 70 | 71 | var group Group 72 | resp, err := s.client.Do(req, &group) 73 | if err != nil { 74 | return nil, resp, err 75 | } 76 | 77 | return &group, resp, nil 78 | } 79 | 80 | // Update takes an ID and a Group record, updates it, and returns a new Group 81 | // instance or an error. 82 | func (s *GroupService) Update(key string, group *Group) (*Group, *Response, error) { 83 | path := fmt.Sprintf("groups/%s", key) 84 | 85 | req, rerr := s.client.NewRequest("PATCH", path, group) 86 | if rerr != nil { 87 | return nil, nil, rerr 88 | } 89 | 90 | var updatedGroup Group 91 | resp, err := s.client.Do(req, &updatedGroup) 92 | if err != nil { 93 | return nil, resp, err 94 | } 95 | 96 | return &updatedGroup, resp, nil 97 | } 98 | 99 | // Delete the Group identified by the given ID. 100 | func (s *GroupService) Delete(key string) (*Response, error) { 101 | path := fmt.Sprintf("groups/%s", key) 102 | 103 | req, rerr := s.client.NewRequest("DELETE", path, nil) 104 | if rerr != nil { 105 | return nil, rerr 106 | } 107 | 108 | resp, err := s.client.Do(req, nil) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | return resp, nil 114 | } 115 | -------------------------------------------------------------------------------- /group_service_test.go: -------------------------------------------------------------------------------- 1 | package adafruitio 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestGroupAll(t *testing.T) { 12 | setup() 13 | defer teardown() 14 | 15 | mux.HandleFunc(serverPattern("groups"), 16 | func(w http.ResponseWriter, r *http.Request) { 17 | testMethod(t, r, "GET") 18 | fmt.Fprint(w, `[ 19 | { 20 | "id": 1, 21 | "name": "group", 22 | "key": "group", 23 | "description": null, 24 | "source": null, 25 | "properties": null, 26 | "source_keys": [ 27 | "temperature", 28 | "pressure", 29 | "humidity", 30 | "temp_min", 31 | "temp_max", 32 | "wind_speed", 33 | "wind_direction", 34 | "rainfall", 35 | "name" 36 | ], 37 | "created_at": "2016-05-26T18:50:09.695Z", 38 | "updated_at": "2016-05-27T15:08:11.661Z", 39 | "feeds": [ 40 | { 41 | "id": 1, 42 | "name": "beta-test", 43 | "key": "beta-test", 44 | "unit_type": null, 45 | "unit_symbol": null, 46 | "mode": null, 47 | "history": true, 48 | "last_value": "67.123441", 49 | "last_value_at": "2016-05-26T14:37:14.306Z", 50 | "created_at": "2016-05-23T18:01:39.753Z", 51 | "updated_at": "2016-05-27T15:08:11.663Z", 52 | "stream": { 53 | "id": 565778728, 54 | "value": "67.123441", 55 | "lat": null, 56 | "lon": null, 57 | "ele": null, 58 | "completed_at": null, 59 | "created_at": "2016-05-26T14:37:14.306Z" 60 | } 61 | } 62 | ] 63 | } 64 | ]`) 65 | }, 66 | ) 67 | 68 | assert := assert.New(t) 69 | 70 | groups, response, err := client.Group.All() 71 | 72 | assert.Nil(err) 73 | assert.NotNil(groups) 74 | assert.NotNil(response) 75 | 76 | group := groups[0] 77 | 78 | assert.Equal(1, group.ID) 79 | assert.Equal("group", group.Name) 80 | } 81 | 82 | func TestGroupGet(t *testing.T) { 83 | setup() 84 | defer teardown() 85 | 86 | mux.HandleFunc(serverPattern("groups/test"), 87 | func(w http.ResponseWriter, r *http.Request) { 88 | testMethod(t, r, "GET") 89 | fmt.Fprint(w, `{"id":1, "name":"test", "feeds": [{"id": 1}]}`) 90 | }, 91 | ) 92 | 93 | assert := assert.New(t) 94 | 95 | group, response, err := client.Group.Get("test") 96 | 97 | assert.Nil(err) 98 | assert.NotNil(group) 99 | assert.NotNil(response) 100 | 101 | assert.Equal(1, group.ID) 102 | assert.Equal("test", group.Name) 103 | } 104 | 105 | func TestGroupCreate(t *testing.T) { 106 | setup() 107 | defer teardown() 108 | 109 | mux.HandleFunc(serverPattern("groups"), 110 | func(w http.ResponseWriter, r *http.Request) { 111 | testMethod(t, r, "POST") 112 | fmt.Fprint(w, `{"id":1, "name":"test"}`) 113 | }, 114 | ) 115 | 116 | assert := assert.New(t) 117 | 118 | ngroup := &Group{Name: "test"} 119 | 120 | group, response, err := client.Group.Create(ngroup) 121 | 122 | assert.Nil(err) 123 | assert.NotNil(group) 124 | assert.NotNil(response) 125 | 126 | assert.Equal(1, group.ID) 127 | assert.Equal("test", group.Name) 128 | } 129 | 130 | func TestGroupUpdate(t *testing.T) { 131 | setup() 132 | defer teardown() 133 | 134 | mux.HandleFunc(serverPattern("groups/test"), 135 | func(w http.ResponseWriter, r *http.Request) { 136 | testMethod(t, r, "PATCH") 137 | fmt.Fprint(w, `{"id":1, "name":"test-2"}`) 138 | }, 139 | ) 140 | 141 | assert := assert.New(t) 142 | 143 | group := &Group{ID: 1, Name: "test"} 144 | 145 | ugroup, response, err := client.Group.Update("test", group) 146 | 147 | assert.Nil(err) 148 | assert.NotNil(ugroup) 149 | assert.NotNil(response) 150 | 151 | assert.Equal(1, ugroup.ID) 152 | assert.Equal("test-2", ugroup.Name) 153 | assert.NotEqual(&group, &ugroup) 154 | } 155 | 156 | func TestGroupDelete(t *testing.T) { 157 | setup() 158 | defer teardown() 159 | 160 | mux.HandleFunc(serverPattern("groups/test"), 161 | func(w http.ResponseWriter, r *http.Request) { 162 | testMethod(t, r, "DELETE") 163 | }, 164 | ) 165 | 166 | assert := assert.New(t) 167 | 168 | response, err := client.Group.Delete("test") 169 | 170 | assert.Nil(err) 171 | assert.NotNil(response) 172 | 173 | assert.Equal(200, response.StatusCode) 174 | } 175 | --------------------------------------------------------------------------------