Skip to content

Commit

Permalink
Merge pull request #2 from d-dot-one/AWN-2.fix-module
Browse files Browse the repository at this point in the history
project setup and client refactor
  • Loading branch information
d-dot-one committed Nov 1, 2023
2 parents 4868f89 + 72bc78d commit b6d510b
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 69 deletions.
25 changes: 25 additions & 0 deletions .github/ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Thank your for caring and for creating an issue. Please do not submit security issues here, but do follow the
[Security Policy](../SECURITY.md).

## Checklist
- [ ] Is this something you can **debug and fix**? Submit a pull request! Bug fixes and documentation fixes are welcome.
- [ ] Is this a security issue? Review our [Security Policy](../SECURITY.md).
- [ ] Have an idea for a feature? Post the feature request as an Issue (right here).

## Create a Bug Report

Make sure to add **all the information needed to understand the bug** so that someone can help. If the info is
missing we'll add the 'Needs more information' label and close the issue until there is enough information to
understand and triage the bug.

- [ ] Provide a **minimal code snippet**
- [ ] Provide **screenshots** where appropriate
- [ ] Are you using Linux, Mac, or Windows?

## Description

## Code Snippets

## Screenshots

## Other Information
21 changes: 21 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!-- You can erase any parts of this template not applicable to your Pull Request. --->

## Description
<!--- Explain the details for making this change. What existing problem does the pull request solve? --->

## Related Issue
<!--- If suggesting a new feature or change, please discuss it in an issue first --->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce --->
<!--- Put `closes #XX` in your comment to auto-close the issue that your PR fixes --->
<!--- Link to the issue here: --->
[//]: # (* [ ] Have you followed the guidelines in our Contributing document?)
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/d-dot-one/publish-to-aws-sns-action/pulls) for the same update/change?

## Pull Request Checklist:

* [ ] Did you write tests for your changes?
* [ ] Does your submission pass all tests?
* [ ] Did you run `pre-commit` before submission?
* [ ] Did you override or ignore any linting issues?
* [ ] Do your changes pass all workflow steps?
* [ ] Did you update the documentation?
1 change: 1 addition & 0 deletions .github/workflows/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @d-dot-one d-dot-one@proton.me
File renamed without changes.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
# Go workspace and environment
go.work
.envrc

# ide
.idea/
Expand Down
9 changes: 4 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Change these variables as necessary.
# Stolen from https://www.alexedwards.net/blog/a-time-saving-makefile-for-your-go-projects
#
MAIN_PACKAGE_PATH := ./cmd/example
BINARY_NAME := example
MAIN_PACKAGE_PATH := .
BINARY_NAME := awn

# ==================================================================================== #
# HELPERS
Expand Down Expand Up @@ -58,15 +58,14 @@ test:
go test -v -race -buildvcs ./...

## test/cover: run all tests and display coverage
.PHONY: test/cover
test/cover:
.PHONY: cover
cover:
go test -v -race -buildvcs -coverprofile=/tmp/coverage.out ./...
go tool cover -html=/tmp/coverage.out

## build: build the application
.PHONY: build
build:
# Include additional build steps, like TypeScript, SCSS or Tailwind compilation here...
go build -o=/tmp/bin/${BINARY_NAME} ${MAIN_PACKAGE_PATH}

## run: run the application
Expand Down
91 changes: 63 additions & 28 deletions client/client.go → client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Package client is a client that can access the Ambient Weather network API and
// return device and weather data.
package client
// Copyright 2023 d-dot-one. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// Package ambient_weather_client is a client that can access the Ambient Weather
// network API and return device and weather data.
package awn

import (
"context"
Expand All @@ -12,7 +16,7 @@ import (
"sync"
"time"

"gopkg.in/resty.v1"
"github.com/go-resty/resty/v2"
)

const (
Expand Down Expand Up @@ -59,51 +63,53 @@ const (
// The createAwnClient function is used to create a new resty-based API client. This client
// supports retries and can be placed into debug mode when needed. By default, it will
// also set the accept content type to JSON. Finally, it returns a pointer to the client.
func createAwnClient() *resty.Client {
func createAwnClient() (*resty.Client, error) {
client := resty.New().
SetRetryCount(retryCount).
SetRetryWaitTime(retryMinWaitTimeSeconds * time.Second).
SetRetryMaxWaitTime(retryMaxWaitTimeSeconds * time.Second).
SetHostURL(baseURL + apiVersion).
SetBaseURL(baseURL + apiVersion).
SetTimeout(defaultCtxTimeout * time.Second).
SetDebug(debugMode).
AddRetryCondition(
func(r *resty.Response) (bool, error) {
func(r *resty.Response, e error) bool {
return r.StatusCode() == http.StatusRequestTimeout ||
r.StatusCode() >= http.StatusInternalServerError ||
r.StatusCode() == http.StatusTooManyRequests, error(nil)
r.StatusCode() == http.StatusTooManyRequests
})

client.SetHeader("Accept", "application/json")

return client
return client, nil
}

// CreateAPIConfig is a helper function that is used to create the FunctionData struct,
// which is passed to the data gathering functions.
func CreateAPIConfig(api string, app string) FunctionData {
functionData := FunctionData{
Api: api,
App: app,
Ct: createAwnClient(),
}

return functionData
// which is passed to the data gathering functions. It takes as parameters the API key
// as api and the Application key as app and returns a pointer to a FunctionData object.
func CreateAPIConfig(api string, app string) *FunctionData {
fd := NewFunctionData()
fd.API = api
fd.App = app

return fd
}

// The GetDevices function takes a client, sets the appropriate query parameters for
// authentication, makes the request to the devicesEndpoint endpoint and marshals the
// GetDevices is a public function takes a client, sets the appropriate query parameters
// for authentication, makes the request to the devicesEndpoint endpoint and marshals the
// response data into a pointer to an AmbientDevice object, which is returned along with
// any error messages.
func GetDevices(ctx context.Context, funcData FunctionData) (AmbientDevice, error) {
funcData.Ct.SetQueryParams(map[string]string{
"apiKey": funcData.Api,
client, err := createAwnClient()
CheckReturn(err, "unable to create client", "warning")

client.R().SetQueryParams(map[string]string{
"apiKey": funcData.API,
"applicationKey": funcData.App,
})

deviceData := &AmbientDevice{}

_, err := funcData.Ct.R().SetResult(deviceData).Get(devicesEndpoint)
_, err = client.R().SetResult(deviceData).Get(devicesEndpoint)
CheckReturn(err, "unable to handle data from devicesEndpoint", "warning")

if errors.Is(ctx.Err(), context.DeadlineExceeded) {
Expand All @@ -119,16 +125,19 @@ func GetDevices(ctx context.Context, funcData FunctionData) (AmbientDevice, erro
// data is then marshaled into a pointer to a DeviceDataResponse object which is
// returned to the caller along with any errors.
func getDeviceData(ctx context.Context, funcData FunctionData) (DeviceDataResponse, error) {
funcData.Ct.SetQueryParams(map[string]string{
"apiKey": funcData.Api,
client, err := createAwnClient()
CheckReturn(err, "unable to create client", "warning")

client.R().SetQueryParams(map[string]string{
"apiKey": funcData.API,
"applicationKey": funcData.App,
"endDate": strconv.FormatInt(funcData.Epoch, 10),
"limit": strconv.Itoa(funcData.Limit),
})

deviceData := &DeviceDataResponse{}

_, err := funcData.Ct.R().
_, err = client.R().
SetPathParams(map[string]string{
"devicesEndpoint": devicesEndpoint,
"macAddress": funcData.Mac,
Expand All @@ -150,8 +159,8 @@ func getDeviceData(ctx context.Context, funcData FunctionData) (DeviceDataRespon
return *deviceData, err
}

// The GetHistoricalData function takes a FunctionData object as input and returns a and
// will return a list of client.DeviceDataResponse object.
// GetHistoricalData is a public function takes a FunctionData object as input and
// returns a and will return a list of client.DeviceDataResponse object.
func GetHistoricalData(ctx context.Context, funcData FunctionData) ([]DeviceDataResponse, error) {
var deviceResponse []DeviceDataResponse

Expand Down Expand Up @@ -187,6 +196,32 @@ func CheckReturn(err error, msg string, level LogLevelForError) {
}
}

// CheckResponse is a helper function that will take an API response and evaluate it for
// for any errors that might have occurred. The API specification does not publish all of
// the possible error messages, but these are what I have found so far.
func CheckResponse(resp map[string]string) bool {
message, ok := resp["error"]
if ok {
switch message {
case "apiKey-missing":
log.Panicf("API key is missing (%v). Visit https://ambientweather.net/account", message)
case "applicationKey-missing":
log.Panicf("App key is missing (%v). Visit https://ambientweather.net/account", message)
case "date-invalid":
log.Panicf("Date is invalid (%v). It should be in epoch time in milliseconds", message)
case "macAddress-missing":
log.Panicf("MAC address is missing (%v). Supply a valid MAC address for a weather station", message)
default:
return true
}
}

return true
}

// GetHistoricalDataAsync is a public function that takes a context object, a FunctionData
// object and a WaitGroup object as inputs. It will return a channel of DeviceDataResponse
// objects and an error status.
func GetHistoricalDataAsync(
ctx context.Context,
funcData FunctionData,
Expand Down
9 changes: 4 additions & 5 deletions client/client_test.go → client_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package client
package awn

import (
"context"
Expand Down Expand Up @@ -33,24 +33,23 @@ func TestCheckReturn(t *testing.T) {

func TestCreateApiConfig(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
_, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()

type args struct {
api string
app string
ctx context.Context
}
tests := []struct {
name string
args args
want FunctionData
}{
{"TestCreateApiConfig", args{"api", "app", ctx}, FunctionData{"api", "app", nil, ctx, 0, 0, ""}},
{name: "TestCreateApiConfig", args: args{"api", "app"}, want: FunctionData{"api", "app", 0, 1, ""}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := CreateAPIConfig(tt.args.ctx, tt.args.api, tt.args.app); !reflect.DeepEqual(got, tt.want) {
if got := CreateAPIConfig(tt.args.api, tt.args.app); !reflect.DeepEqual(got, tt.want) {
t.Errorf("CreateAPIConfig() = %v, want %v", got, tt.want)
}
})
Expand Down
25 changes: 12 additions & 13 deletions client/data_structures.go → data_structures.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
package client
package awn

import (
"encoding/json"
"fmt"
"time"

"gopkg.in/resty.v1"
)

// FunctionData is a struct that is used to pass data to the getDeviceData function.
// FunctionData is a struct that is used to pass data basic API call parameters more
// easily. It contains API (API key), App (Application key), Epoch (Unix epoch time in
// milliseconds), Limit (maximum number of records to return in a single API call), and
// Mac (MAC address of the weather station.
type FunctionData struct {
Api string `json:"api"`
App string `json:"app"`
Ct *resty.Client `json:"ct"`
Epoch int64 `json:"epoch"`
Limit int `json:"limit"`
Mac string `json:"mac"`
API string `json:"api"`
App string `json:"app"`
Epoch int64 `json:"epoch"`
Limit int `json:"limit"`
Mac string `json:"mac"`
}

// String is a helper function to print the FunctionData struct as a string.
Expand All @@ -27,11 +27,10 @@ func (f FunctionData) String() string {

// NewFunctionData creates a new FunctionData object with some default values and return
// it to the caller as a pointer.
func (f FunctionData) NewFunctionData(client *resty.Client) *FunctionData {
func NewFunctionData() *FunctionData {
return &FunctionData{
Api: "",
API: "",
App: "",
Ct: client,
Epoch: 0,
Limit: 1,
Mac: "",
Expand Down
Loading

0 comments on commit b6d510b

Please sign in to comment.