diff --git a/client/client.go b/client/client.go index bad1ed8..ea568c4 100644 --- a/client/client.go +++ b/client/client.go @@ -7,13 +7,11 @@ import ( "fmt" "io" "net/http" - "runtime" + "github.com/andybalholm/brotli" "github.com/jsdelivr/globalping-cli/model" ) -const userAgent = "Globalping API Go Client / v1" + " (" + runtime.GOOS + "/" + runtime.GOARCH + ")" - var ApiUrl = "https://api.globalping.io/v1/measurements" // Post measurement to Globalping API - boolean indicates whether to print CLI help on error @@ -29,7 +27,8 @@ func PostAPI(measurement model.PostMeasurement) (model.PostResponse, bool, error if err != nil { return model.PostResponse{}, false, errors.New("err: failed to create request - please report this bug") } - req.Header.Set("User-Agent", userAgent) + req.Header.Set("User-Agent", userAgent()) + req.Header.Set("Accept-Encoding", "br") req.Header.Set("Content-Type", "application/json") // Make the request @@ -73,8 +72,14 @@ func PostAPI(measurement model.PostMeasurement) (model.PostResponse, bool, error } // Read the response body + + var bodyReader io.Reader = resp.Body + if resp.Header.Get("Content-Encoding") == "br" { + bodyReader = brotli.NewReader(bodyReader) + } + var data model.PostResponse - err = json.NewDecoder(resp.Body).Decode(&data) + err = json.NewDecoder(bodyReader).Decode(&data) if err != nil { fmt.Println(err) return model.PostResponse{}, false, errors.New("err: invalid post measurement format returned - please report this bug") @@ -100,76 +105,3 @@ func DecodeTimings(cmd string, timings json.RawMessage) (model.Timings, error) { return data, nil } - -// Get measurement from Globalping API -func GetAPI(id string) (model.GetMeasurement, error) { - // Create a new request - req, err := http.NewRequest("GET", ApiUrl+"/"+id, nil) - if err != nil { - return model.GetMeasurement{}, errors.New("err: failed to create request") - } - req.Header.Set("User-Agent", userAgent) - - // Make the request - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return model.GetMeasurement{}, errors.New("err: request failed") - } - defer resp.Body.Close() - - // 404 not found - if resp.StatusCode == http.StatusNotFound { - return model.GetMeasurement{}, errors.New("err: measurement not found") - } - - // 500 error - if resp.StatusCode == http.StatusInternalServerError { - return model.GetMeasurement{}, errors.New("err: internal server error - please try again later") - } - - // Read the response body - var data model.GetMeasurement - err = json.NewDecoder(resp.Body).Decode(&data) - if err != nil { - return model.GetMeasurement{}, errors.New("invalid get measurement format returned") - } - - return data, nil -} - -func GetApiJson(id string) (string, error) { - // Create a new request - req, err := http.NewRequest("GET", ApiUrl+"/"+id, nil) - if err != nil { - return "", errors.New("err: failed to create request") - } - req.Header.Set("User-Agent", userAgent) - - // Make the request - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", errors.New("err: request failed") - } - defer resp.Body.Close() - - // 404 not found - if resp.StatusCode == http.StatusNotFound { - return "", errors.New("err: measurement not found") - } - - // 500 error - if resp.StatusCode == http.StatusInternalServerError { - return "", errors.New("err: internal server error - please try again later") - } - - // Read the response body - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return "", errors.New("err: failed to read response body") - } - respString := string(respBytes) - - return respString, nil -} diff --git a/client/client_test.go b/client/client_test.go index 94f95d2..9d8e800 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -137,7 +137,9 @@ func testGetValid(t *testing.T) { defer server.Close() client.ApiUrl = server.URL - res, err := client.GetAPI("abcd") + fetcher := client.NewMeasurementsFetcher(server.URL) + + res, err := fetcher.GetMeasurement("abcd") if err != nil { t.Error(err) } @@ -152,12 +154,14 @@ func testGetJson(t *testing.T) { defer server.Close() client.ApiUrl = server.URL - res, err := client.GetApiJson("abcd") + fetcher := client.NewMeasurementsFetcher(server.URL) + + res, err := fetcher.GetRawMeasurement("abcd") if err != nil { t.Error(err) } - assert.Equal(t, `{"id":"abcd"}`, res) + assert.Equal(t, `{"id":"abcd"}`, string(res)) } func testGetPing(t *testing.T) { @@ -206,7 +210,9 @@ func testGetPing(t *testing.T) { defer server.Close() client.ApiUrl = server.URL - res, err := client.GetAPI("abcd") + fetcher := client.NewMeasurementsFetcher(server.URL) + + res, err := fetcher.GetMeasurement("abcd") if err != nil { t.Error(err) } @@ -299,7 +305,9 @@ func testGetTraceroute(t *testing.T) { defer server.Close() client.ApiUrl = server.URL - res, err := client.GetAPI("abcd") + fetcher := client.NewMeasurementsFetcher(server.URL) + + res, err := fetcher.GetMeasurement("abcd") if err != nil { t.Error(err) } @@ -375,7 +383,9 @@ func testGetDns(t *testing.T) { defer server.Close() client.ApiUrl = server.URL - res, err := client.GetAPI("abcd") + fetcher := client.NewMeasurementsFetcher(server.URL) + + res, err := fetcher.GetMeasurement("abcd") if err != nil { t.Error(err) } @@ -498,7 +508,9 @@ func testGetMtr(t *testing.T) { defer server.Close() client.ApiUrl = server.URL - res, err := client.GetAPI("abcd") + fetcher := client.NewMeasurementsFetcher(server.URL) + + res, err := fetcher.GetMeasurement("abcd") if err != nil { t.Error(err) } @@ -603,7 +615,9 @@ func testGetHttp(t *testing.T) { defer server.Close() client.ApiUrl = server.URL - res, err := client.GetAPI("abcd") + fetcher := client.NewMeasurementsFetcher(server.URL) + + res, err := fetcher.GetMeasurement("abcd") if err != nil { t.Error(err) } diff --git a/client/measurements_fetcher.go b/client/measurements_fetcher.go new file mode 100644 index 0000000..7c123e4 --- /dev/null +++ b/client/measurements_fetcher.go @@ -0,0 +1,124 @@ +package client + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/andybalholm/brotli" + "github.com/jsdelivr/globalping-cli/model" +) + +type MeasurementsFetcher interface { + GetMeasurement(id string) (*model.GetMeasurement, error) + GetRawMeasurement(id string) ([]byte, error) +} + +type measurementsFetcher struct { + // The api url endpoint + apiUrl string + + // http client + cl *http.Client + + // caches Etags by measurement id + etags map[string]string + + // caches Measurements by ETag + measurements map[string][]byte +} + +func NewMeasurementsFetcher(apiUrl string) *measurementsFetcher { + return &measurementsFetcher{ + apiUrl: apiUrl, + cl: &http.Client{}, + etags: map[string]string{}, + measurements: map[string][]byte{}, + } +} + +// GetRawMeasurement returns API response as a GetMeasurement object +func (f *measurementsFetcher) GetMeasurement(id string) (*model.GetMeasurement, error) { + respBytes, err := f.GetRawMeasurement(id) + if err != nil { + return nil, err + } + + var m model.GetMeasurement + err = json.Unmarshal(respBytes, &m) + if err != nil { + return nil, fmt.Errorf("invalid get measurement format returned: %v %s", err, string(respBytes)) + } + + return &m, nil +} + +// GetRawMeasurement returns the API response's raw json response +func (f *measurementsFetcher) GetRawMeasurement(id string) ([]byte, error) { + // Create a new request + req, err := http.NewRequest("GET", f.apiUrl+"/"+id, nil) + if err != nil { + return nil, errors.New("err: failed to create request") + } + + req.Header.Set("User-Agent", userAgent()) + req.Header.Set("Accept-Encoding", "br") + + etag := f.etags[id] + if etag != "" { + req.Header.Set("If-None-Match", etag) + } + + // Make the request + resp, err := f.cl.Do(req) + if err != nil { + return nil, errors.New("err: request failed") + } + defer resp.Body.Close() + + // 404 not found + if resp.StatusCode == http.StatusNotFound { + return nil, errors.New("err: measurement not found") + } + + // 500 error + if resp.StatusCode == http.StatusInternalServerError { + return nil, errors.New("err: internal server error - please try again later") + } + + // 304 not modified + if resp.StatusCode == http.StatusNotModified { + // get response bytes from cache + respBytes := f.measurements[etag] + if respBytes == nil { + return nil, errors.New("err: response not found in etags cache") + } + + return respBytes, nil + } + + if resp.StatusCode >= 400 { + return nil, fmt.Errorf("err: response code %d", resp.StatusCode) + } + + var bodyReader io.Reader = resp.Body + + if resp.Header.Get("Content-Encoding") == "br" { + bodyReader = brotli.NewReader(bodyReader) + } + + // Read the response body + respBytes, err := io.ReadAll(bodyReader) + if err != nil { + return nil, errors.New("err: failed to read response body") + } + + // save etag and response to cache + etag = resp.Header.Get("ETag") + f.etags[id] = etag + f.measurements[etag] = respBytes + + return respBytes, nil +} diff --git a/client/measurements_fetcher_test.go b/client/measurements_fetcher_test.go new file mode 100644 index 0000000..f61785c --- /dev/null +++ b/client/measurements_fetcher_test.go @@ -0,0 +1,103 @@ +package client + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/andybalholm/brotli" + "github.com/jsdelivr/globalping-cli/model" + "github.com/stretchr/testify/assert" +) + +func TestFetchWithEtag(t *testing.T) { + id1 := "123abc" + id2 := "567xyz" + + cacheMissCount := 0 + cacheHitCount := 0 + + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + parts := strings.Split(r.URL.Path, "/") + id := parts[len(parts)-1] + + etag := func(id string) string { + return "etag-" + id + } + + if r.Header.Get("If-None-Match") == etag(id) { + // cache hit + cacheHitCount++ + w.Header().Set("ETag", etag(id)) + w.WriteHeader(http.StatusNotModified) + + return + } + + // cache miss, return full response + cacheMissCount++ + m := &model.GetMeasurement{ + ID: id, + } + + w.Header().Set("ETag", etag(id)) + + err := json.NewEncoder(w).Encode(m) + assert.NoError(t, err) + })) + + f := NewMeasurementsFetcher(s.URL) + + // first request for id1 + m, err := f.GetMeasurement(id1) + assert.NoError(t, err) + + assert.Equal(t, id1, m.ID) + + // first request for id1 + m, err = f.GetMeasurement(id2) + assert.NoError(t, err) + + assert.Equal(t, id2, m.ID) + + // second request for id1 + m, err = f.GetMeasurement(id2) + assert.NoError(t, err) + + assert.Equal(t, id2, m.ID) + + assert.Equal(t, 1, cacheHitCount) + assert.Equal(t, 2, cacheMissCount) +} + +func TestFetchWithBrotli(t *testing.T) { + id := "123abc" + + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + parts := strings.Split(r.URL.Path, "/") + id := parts[len(parts)-1] + + assert.Equal(t, "br", r.Header.Get("Accept-Encoding")) + + m := &model.GetMeasurement{ + ID: id, + } + + w.Header().Set("Content-Encoding", "br") + + rW := brotli.NewWriter(w) + defer rW.Close() + + err := json.NewEncoder(rW).Encode(m) + assert.NoError(t, err) + })) + + f := NewMeasurementsFetcher(s.URL) + + m, err := f.GetMeasurement(id) + assert.NoError(t, err) + + assert.Equal(t, id, m.ID) +} diff --git a/client/user_agent.go b/client/user_agent.go new file mode 100644 index 0000000..825f6c7 --- /dev/null +++ b/client/user_agent.go @@ -0,0 +1,11 @@ +package client + +import ( + "fmt" + + "github.com/jsdelivr/globalping-cli/version" +) + +func userAgent() string { + return fmt.Sprintf("globalping-cli/v%s (https://github.com/jsdelivr/globalping-cli)", version.Version) +} diff --git a/client/user_agent_test.go b/client/user_agent_test.go new file mode 100644 index 0000000..96b5a18 --- /dev/null +++ b/client/user_agent_test.go @@ -0,0 +1,13 @@ +package client + +import ( + "testing" + + "github.com/jsdelivr/globalping-cli/version" + "github.com/stretchr/testify/assert" +) + +func TestUserAgent(t *testing.T) { + version.Version = "x.y.z" + assert.Equal(t, "globalping-cli/vx.y.z (https://github.com/jsdelivr/globalping-cli)", userAgent()) +} diff --git a/cmd/dns.go b/cmd/dns.go index 4f5d58c..5723312 100644 --- a/cmd/dns.go +++ b/cmd/dns.go @@ -5,6 +5,7 @@ import ( "github.com/jsdelivr/globalping-cli/client" "github.com/jsdelivr/globalping-cli/model" + "github.com/jsdelivr/globalping-cli/view" "github.com/spf13/cobra" ) @@ -70,7 +71,7 @@ Examples: return nil } - client.OutputResults(res.ID, ctx) + view.OutputResults(res.ID, ctx) return nil }, } diff --git a/cmd/http.go b/cmd/http.go index ac46885..dbd7540 100644 --- a/cmd/http.go +++ b/cmd/http.go @@ -9,6 +9,7 @@ import ( "github.com/jsdelivr/globalping-cli/client" "github.com/jsdelivr/globalping-cli/model" + "github.com/jsdelivr/globalping-cli/view" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -136,7 +137,7 @@ func httpCmdRun(cmd *cobra.Command, args []string) error { return nil } - client.OutputResults(res.ID, ctx) + view.OutputResults(res.ID, ctx) return nil } diff --git a/cmd/mtr.go b/cmd/mtr.go index 583909b..0704a3b 100644 --- a/cmd/mtr.go +++ b/cmd/mtr.go @@ -5,6 +5,7 @@ import ( "github.com/jsdelivr/globalping-cli/client" "github.com/jsdelivr/globalping-cli/model" + "github.com/jsdelivr/globalping-cli/view" "github.com/spf13/cobra" ) @@ -62,7 +63,7 @@ Examples: return nil } - client.OutputResults(res.ID, ctx) + view.OutputResults(res.ID, ctx) return nil }, } diff --git a/cmd/ping.go b/cmd/ping.go index 1344e61..8f93372 100644 --- a/cmd/ping.go +++ b/cmd/ping.go @@ -5,6 +5,7 @@ import ( "github.com/jsdelivr/globalping-cli/client" "github.com/jsdelivr/globalping-cli/model" + "github.com/jsdelivr/globalping-cli/view" "github.com/spf13/cobra" ) @@ -56,7 +57,7 @@ Examples: return nil } - client.OutputResults(res.ID, ctx) + view.OutputResults(res.ID, ctx) return nil }, } diff --git a/cmd/root.go b/cmd/root.go index e2249bb..3c1cd4f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -25,9 +25,8 @@ var ( // TODO: headers map[string]string - opts = model.PostMeasurement{} - ctx = model.Context{} - version string + opts = model.PostMeasurement{} + ctx = model.Context{} ) // rootCmd represents the base command when called without any subcommands @@ -40,9 +39,7 @@ The CLI tool allows you to interact with the API in a simple and human-friendly // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute(ver string) { - version = ver - +func Execute() { rootCmd.AddGroup(&cobra.Group{ID: "Measurements", Title: "Measurement Commands:"}) err := rootCmd.Execute() if err != nil { diff --git a/cmd/traceroute.go b/cmd/traceroute.go index 3a70017..5731e01 100644 --- a/cmd/traceroute.go +++ b/cmd/traceroute.go @@ -5,6 +5,7 @@ import ( "github.com/jsdelivr/globalping-cli/client" "github.com/jsdelivr/globalping-cli/model" + "github.com/jsdelivr/globalping-cli/view" "github.com/spf13/cobra" ) @@ -64,7 +65,7 @@ Examples: return nil } - client.OutputResults(res.ID, ctx) + view.OutputResults(res.ID, ctx) return nil }, } diff --git a/cmd/version.go b/cmd/version.go index ae6221b..9a21fa6 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" + "github.com/jsdelivr/globalping-cli/version" "github.com/spf13/cobra" ) @@ -14,6 +15,6 @@ var versionCmd = &cobra.Command{ Use: "version", Short: "Print the version number of Globalping CLI", Run: func(cmd *cobra.Command, args []string) { - fmt.Println("Globalping CLI v" + version) + fmt.Println("Globalping CLI v" + version.Version) }, } diff --git a/go.mod b/go.mod index 827a635..7c8dbd4 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( require ( atomicgo.dev/cursor v0.1.1 // indirect atomicgo.dev/keyboard v0.2.9 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/containerd/console v1.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index 7d19c53..4a925ab 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzX github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= diff --git a/main.go b/main.go index 523a6c1..15c0cc2 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,9 @@ package main -import "github.com/jsdelivr/globalping-cli/cmd" +import ( + "github.com/jsdelivr/globalping-cli/cmd" + pkgversion "github.com/jsdelivr/globalping-cli/version" +) var ( // https://goreleaser.com/cookbooks/using-main.version/ @@ -8,5 +11,6 @@ var ( ) func main() { - cmd.Execute(version) + pkgversion.Version = version + cmd.Execute() } diff --git a/version/version.go b/version/version.go new file mode 100644 index 0000000..8b26349 --- /dev/null +++ b/version/version.go @@ -0,0 +1,4 @@ +package version + +// Tool Version +var Version string diff --git a/client/view.go b/view/view.go similarity index 82% rename from client/view.go rename to view/view.go index cfa3562..27765fa 100644 --- a/client/view.go +++ b/view/view.go @@ -1,4 +1,4 @@ -package client +package view import ( "fmt" @@ -7,6 +7,7 @@ import ( "time" "github.com/charmbracelet/lipgloss" + "github.com/jsdelivr/globalping-cli/client" "github.com/jsdelivr/globalping-cli/model" "github.com/pterm/pterm" ) @@ -74,9 +75,9 @@ func generateHeader(result model.MeasurementResponse, ctx model.Context) string } } -var liveViewPollInterval = 100 * time.Millisecond +var apiPollInterval = 500 * time.Millisecond -func LiveView(id string, data model.GetMeasurement, ctx model.Context) { +func LiveView(id string, data *model.GetMeasurement, ctx model.Context) { var err error // Create new writer @@ -104,10 +105,12 @@ func LiveView(id string, data model.GetMeasurement, ctx model.Context) { // String builder for output var output strings.Builder + fetcher := client.NewMeasurementsFetcher(client.ApiUrl) + // Poll API until the measurement is complete for data.Status == "in-progress" { - time.Sleep(liveViewPollInterval) - data, err = GetAPI(id) + time.Sleep(apiPollInterval) + data, err = fetcher.GetMeasurement(id) if err != nil { fmt.Printf("failed to get data: %v\n", err) return @@ -139,17 +142,17 @@ func LiveView(id string, data model.GetMeasurement, ctx model.Context) { } // If json flag is used, only output json -func OutputJson(id string) { - output, err := GetApiJson(id) +func OutputJson(id string, fetcher client.MeasurementsFetcher) { + output, err := fetcher.GetRawMeasurement(id) if err != nil { fmt.Println(err) return } - fmt.Println(output) + fmt.Println(string(output)) } // If latency flag is used, only output latency values -func OutputLatency(id string, data model.GetMeasurement, ctx model.Context) { +func OutputLatency(id string, data *model.GetMeasurement, ctx model.Context) { // String builder for output var output strings.Builder @@ -166,7 +169,7 @@ func OutputLatency(id string, data model.GetMeasurement, ctx model.Context) { } if ctx.Cmd == "dns" { - timings, err := DecodeTimings(ctx.Cmd, result.Result.TimingsRaw) + timings, err := client.DecodeTimings(ctx.Cmd, result.Result.TimingsRaw) if err != nil { fmt.Println(err) return @@ -175,7 +178,7 @@ func OutputLatency(id string, data model.GetMeasurement, ctx model.Context) { } if ctx.Cmd == "http" { - timings, err := DecodeTimings(ctx.Cmd, result.Result.TimingsRaw) + timings, err := client.DecodeTimings(ctx.Cmd, result.Result.TimingsRaw) if err != nil { fmt.Println(err) return @@ -195,7 +198,7 @@ func OutputLatency(id string, data model.GetMeasurement, ctx model.Context) { } if ctx.Cmd == "dns" { - timings, err := DecodeTimings(ctx.Cmd, result.Result.TimingsRaw) + timings, err := client.DecodeTimings(ctx.Cmd, result.Result.TimingsRaw) if err != nil { fmt.Println(err) return @@ -204,7 +207,7 @@ func OutputLatency(id string, data model.GetMeasurement, ctx model.Context) { } if ctx.Cmd == "http" { - timings, err := DecodeTimings(ctx.Cmd, result.Result.TimingsRaw) + timings, err := client.DecodeTimings(ctx.Cmd, result.Result.TimingsRaw) if err != nil { fmt.Println(err) return @@ -223,7 +226,7 @@ func OutputLatency(id string, data model.GetMeasurement, ctx model.Context) { fmt.Println(strings.TrimSpace(output.String())) } -func OutputCI(id string, data model.GetMeasurement, ctx model.Context) { +func OutputCI(id string, data *model.GetMeasurement, ctx model.Context) { // String builder for output var output strings.Builder @@ -240,8 +243,10 @@ func OutputCI(id string, data model.GetMeasurement, ctx model.Context) { } func OutputResults(id string, ctx model.Context) { + fetcher := client.NewMeasurementsFetcher(client.ApiUrl) + // Wait for first result to arrive from a probe before starting display (can be in-progress) - data, err := GetAPI(id) + data, err := fetcher.GetMeasurement(id) if err != nil { fmt.Println(err) return @@ -249,8 +254,8 @@ func OutputResults(id string, ctx model.Context) { // Probe may not have started yet for len(data.Results) == 0 { - time.Sleep(100 * time.Millisecond) - data, err = GetAPI(id) + time.Sleep(apiPollInterval) + data, err = fetcher.GetMeasurement(id) if err != nil { fmt.Println(err) return @@ -258,29 +263,29 @@ func OutputResults(id string, ctx model.Context) { } if ctx.CI || ctx.JsonOutput || ctx.Latency { - // Poll API every 100 milliseconds until the measurement is complete + // Poll API until the measurement is complete for data.Status == "in-progress" { - time.Sleep(100 * time.Millisecond) - data, err = GetAPI(id) + time.Sleep(apiPollInterval) + data, err = fetcher.GetMeasurement(id) if err != nil { fmt.Println(err) return } } + switch { + case ctx.CI: + OutputCI(id, data, ctx) + return + case ctx.JsonOutput: + OutputJson(id, fetcher) + return + case ctx.Latency: + OutputLatency(id, data, ctx) + return + default: + panic(fmt.Sprintf("case not handled. %+v", ctx)) + } } - switch { - case ctx.JsonOutput: - OutputJson(id) - return - case ctx.Latency: - OutputLatency(id, data, ctx) - return - case ctx.CI: - OutputCI(id, data, ctx) - return - default: - LiveView(id, data, ctx) - return - } + LiveView(id, data, ctx) } diff --git a/client/view_test.go b/view/view_test.go similarity index 98% rename from client/view_test.go rename to view/view_test.go index 8a354f0..e9cd518 100644 --- a/client/view_test.go +++ b/view/view_test.go @@ -1,4 +1,4 @@ -package client +package view import ( "testing"