Skip to content

Commit

Permalink
Merge pull request #4 from d-dot-one/AWN-3.rename-module
Browse files Browse the repository at this point in the history
AWN-3 - Minor Module Tweaks
  • Loading branch information
d-dot-one committed Nov 2, 2023
2 parents 7ce2476 + 5cd6a1a commit 64e6438
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 30 deletions.
12 changes: 11 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,18 @@ audit:
# DEVELOPMENT
# ==================================================================================== #

# bench: run benchmarks on all files
.PHONY: bench
bench:
go test -v -bench=. ./... -benchmem

# stat: run benchstat on an iteration of 5
.PHONY: stat
stat:
go test -v -bench=. ./... -benchmem -count=5 | benchstat -

## lint: run linter
.PHONE: lint
.PHONY: lint
lint:
golangci-lint run

Expand Down
103 changes: 78 additions & 25 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,35 @@ const (
retryMinWaitTimeSeconds = 5
)

// The ConvertTimeToEpoch help function can convert any Go time.Time object to a Unix epoch time in milliseconds.
// func ConvertTimeToEpoch(t time.Time) int64 {
// return t.UnixMilli()
//}
// The ConvertTimeToEpoch help function can convert a string, formatted as a time.DateOnly
// object (2023-01-01) to a Unix epoch time in milliseconds. This can be helpful when you
// want to use the GetHistoricalData function to fetch data for a specific date or range
// of dates.
//
// Basic Usage:
//
// epochTime, err := ConvertTimeToEpoch("2023-01-01")
func ConvertTimeToEpoch(t string) (int64, error) {
parsed, err := time.Parse(time.DateOnly, t)
_ = CheckReturn(err, "unable to parse time", "warning")

return parsed.UnixMilli(), err
}

// The createAwnClient function is used to create a new resty-based API client. This client
// 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, error) {
//
// Basic Usage:
//
// client, err := createAwnClient()
func CreateAwnClient() (*resty.Client, error) {
client := resty.New().
SetRetryCount(retryCount).
SetRetryWaitTime(retryMinWaitTimeSeconds * time.Second).
SetRetryMaxWaitTime(retryMaxWaitTimeSeconds * time.Second).
SetBaseURL(baseURL + apiVersion).
SetRetryWaitTime(retryMinWaitTimeSeconds*time.Second).
SetRetryMaxWaitTime(retryMaxWaitTimeSeconds*time.Second).
SetBaseURL(baseURL+apiVersion).
SetHeader("Accept", "application/json").
SetTimeout(defaultCtxTimeout * time.Second).
SetDebug(debugMode).
AddRetryCondition(
Expand All @@ -83,6 +98,10 @@ func createAwnClient() (*resty.Client, error) {
// CreateAPIConfig is a helper function that is used to create the FunctionData struct,
// 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.
//
// Basic Usage:
//
// apiConfig := client.CreateApiConfig("apiTokenHere", "appTokenHere")
func CreateAPIConfig(api string, app string) *FunctionData {
fd := NewFunctionData()
fd.API = api
Expand All @@ -95,9 +114,15 @@ func CreateAPIConfig(api string, app string) *FunctionData {
// 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.
//
// Basic Usage:
//
// ctx := createContext()
// ApiConfig := client.CreateApiConfig(apiKey, appKey)
// data, err := client.GetDevices(ApiConfig)
func GetDevices(ctx context.Context, funcData FunctionData) (AmbientDevice, error) {
client, err := createAwnClient()
CheckReturn(err, "unable to create client", "warning")
client, err := CreateAwnClient()
_ = CheckReturn(err, "unable to create client", "warning")

client.R().SetQueryParams(map[string]string{
"apiKey": funcData.API,
Expand All @@ -107,7 +132,7 @@ func GetDevices(ctx context.Context, funcData FunctionData) (AmbientDevice, erro
deviceData := &AmbientDevice{}

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

if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return nil, errors.New("context timeout exceeded")
Expand All @@ -121,9 +146,15 @@ func GetDevices(ctx context.Context, funcData FunctionData) (AmbientDevice, erro
// number of records to fetch in this API call to the macAddress endpoint. The response
// data is then marshaled into a pointer to a DeviceDataResponse object which is
// returned to the caller along with any errors.
//
// Basic Usage:
//
// ctx := createContext()
// apiConfig := client.CreateApiConfig(apiKey, appKey)
// resp, err := getDeviceData(ctx, apiConfig)
func getDeviceData(ctx context.Context, funcData FunctionData) (DeviceDataResponse, error) {
client, err := createAwnClient()
CheckReturn(err, "unable to create client", "warning")
client, err := CreateAwnClient()
_ = CheckReturn(err, "unable to create client", "warning")

client.R().SetQueryParams(map[string]string{
"apiKey": funcData.API,
Expand All @@ -141,13 +172,9 @@ func getDeviceData(ctx context.Context, funcData FunctionData) (DeviceDataRespon
}).
SetResult(deviceData).
Get("{devicesEndpoint}/{macAddress}")
CheckReturn(err, "unable to handle data from the devices endpoint", "warning")
// todo: check call for errors passed through resp
// if mac is missing, you get the devices endpoint response, so test for mac address
// if apiKey is missing, you get {"error": "apiKey-missing"}
// if appKey is missing, you get {"error": "applicationKey-missing"}
// if date is wrong, you get {"error":"date-invalid","message":"Please refer
// to: http://momentjs.com/docs/#/parsing/string/"}
_ = CheckReturn(err, "unable to handle data from the devices endpoint", "warning")

//CheckResponse(resp) // todo: check call for errors passed through resp

if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return nil, errors.New("ctx timeout exceeded")
Expand All @@ -158,14 +185,20 @@ func getDeviceData(ctx context.Context, funcData FunctionData) (DeviceDataRespon

// GetHistoricalData is a public function takes a FunctionData object as input and
// returns a and will return a list of client.DeviceDataResponse object.
//
// Basic Usage:
//
// ctx := createContext()
// apiConfig := client.CreateApiConfig(apiKey, appKey)
// resp, err := GetHistoricalData(ctx, apiConfig)
func GetHistoricalData(ctx context.Context, funcData FunctionData) ([]DeviceDataResponse, error) {
var deviceResponse []DeviceDataResponse

for i := funcData.Epoch; i <= time.Now().UnixMilli(); i += epochIncrement24h {
funcData.Epoch = i

resp, err := getDeviceData(ctx, funcData)
CheckReturn(err, "unable to get device data", "warning")
_ = CheckReturn(err, "unable to get device data", "warning")

deviceResponse = append(deviceResponse, resp)
}
Expand All @@ -175,8 +208,17 @@ func GetHistoricalData(ctx context.Context, funcData FunctionData) ([]DeviceData

type LogLevelForError string

// CheckReturn is a helper function to remove the usual error checking cruft.
func CheckReturn(err error, msg string, level LogLevelForError) {
// CheckReturn is a helper function to remove the usual error checking cruft while also
// logging the error message. It takes an error, a message and a log level as inputs and
// returns an error (can be nil of course).
//
// Basic Usage:
//
// err = CheckReturn(err, "unable to get device data", "warning")
// if err != nil {
// log.Printf("Error: %v", err)
// }
func CheckReturn(err error, msg string, level LogLevelForError) error {
if err != nil {
switch level {
case "panic":
Expand All @@ -191,11 +233,14 @@ func CheckReturn(err error, msg string, level LogLevelForError) {
log.Printf("%v: %x\n", msg, err)
}
}
return err
}

// CheckResponse is a helper function that will take an API response and evaluate it
// for any errors that might have occurred. The API specification does not publish all
// the possible error messages, but these are what I have found so far.
//
// This is not currently implemented.
func CheckResponse(resp map[string]string) bool {
message, ok := resp["error"]
if ok {
Expand Down Expand Up @@ -232,7 +277,7 @@ func GetHistoricalDataAsync(
funcData.Epoch = i

resp, err := getDeviceData(ctx, funcData)
CheckReturn(err, "unable to get device data", "warning")
_ = CheckReturn(err, "unable to get device data", "warning")

out <- resp
}
Expand All @@ -244,6 +289,10 @@ func GetHistoricalDataAsync(

// GetEnvVars is a public function that will attempt to read the environment variables that
// are passed in as a list of strings. It will return a map of the environment variables.
//
// Basic Usage:
//
// listOfEnvironmentVariables := GetEnvVars([]string{"ENV_VAR_1", "ENV_VAR_2"})
func GetEnvVars(vars []string) map[string]string {
envVars := make(map[string]string)

Expand All @@ -257,6 +306,10 @@ func GetEnvVars(vars []string) map[string]string {

// GetEnvVar is a public function attempts to fetch an environment variable. If that
// environment variable is not found, it will return 'fallback'.
//
// Basic Usage:
//
// environmentVariable := GetEnvVar("ENV_VAR_1", "fallback")
func GetEnvVar(key string, fallback string) string {
value, exists := os.LookupEnv(key)
if !exists {
Expand Down
6 changes: 4 additions & 2 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,18 @@ func TestCreateApiConfig(t *testing.T) {
_, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()

fd := FunctionData{"api", "app", 0, 1, ""}

type args struct {
api string
app string
}
tests := []struct {
name string
args args
want FunctionData
want *FunctionData
}{
{name: "TestCreateApiConfig", args: args{"api", "app"}, want: FunctionData{"api", "app", 0, 1, ""}},
{name: "TestCreateApiConfig", args: args{"api", "app"}, want: &fd},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
11 changes: 11 additions & 0 deletions data_structures.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ func (f FunctionData) String() string {
return fmt.Sprint(string(r))
}

// ToMap is a helper function to convert the FunctionData struct to a map.
func (f FunctionData) ToMap() map[string]interface{} {
return map[string]interface{}{
"api": f.API,
"app": f.App,
"epoch": f.Epoch,
"limit": f.Limit,
"mac": f.Mac,
}
}

// NewFunctionData creates a new FunctionData object with some default values and return
// it to the caller as a pointer.
func NewFunctionData() *FunctionData {
Expand Down
4 changes: 2 additions & 2 deletions data_structures_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ func TestFunctionData_String(t *testing.T) {

//epoch := time.Now().UnixMilli()

_, err := createAwnClient()
CheckReturn(err, "unable to create client", "warning")
_, err := CreateAwnClient()
_ = CheckReturn(err, "unable to create client", "warning")

type fields struct {
API string
Expand Down

0 comments on commit 64e6438

Please sign in to comment.