From 407570d6f6b819777bc00bced54eff05a4d69159 Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Mon, 26 Feb 2024 11:02:11 -0500 Subject: [PATCH 1/4] Add v0 api for LookupResponse and RegisterRespones --- api/v0/api.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 api/v0/api.go diff --git a/api/v0/api.go b/api/v0/api.go new file mode 100644 index 0000000..d54f127 --- /dev/null +++ b/api/v0/api.go @@ -0,0 +1,48 @@ +package v0 + +import ( + v2 "github.com/m-lab/locate/api/v2" + "github.com/m-lab/uuid-annotator/annotator" +) + +// LookupResponse is returned by a lookup request. +type LookupResponse struct { + Error *v2.Error `json:",omitempty"` + Lookup *Lookup `json:",omitempty"` +} + +// Lookup is returned for a successful lookup request. +type Lookup struct { + IATA string +} + +// RegisterResponse is returned by a register request. +type RegisterResponse struct { + Error *v2.Error `json:",omitempty"` + Registration *Registration `json:",omitempty"` +} + +// Network contains IPv4 and IPv6 addresses. +type Network struct { + IPv4 string + IPv6 string +} + +// ServerAnnotation is used by the uuid-annotator. +// From: https://github.com/m-lab/uuid-annotator/blob/main/siteannotator/server.go#L83-L90 +type ServerAnnotation struct { + Annotation annotator.ServerAnnotations + Network Network + Type string +} + +// Registration is returned for a successful registration request. +type Registration struct { + // Hostname is the dynamic DNS name. Hostname should be available immediately. + Hostname string + + // Annotation is the metadata used by the uuid-annotator for all server annotations. + Annotation *ServerAnnotation `json:",omitempty"` + // Heartbeat is the registration message used by the heartbeat service to register with the Locate API. + Heartbeat *v2.Registration `json:",omitempty"` +} From 0145449fd5149d7882302a28a5b336ac35e2beff Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Mon, 26 Feb 2024 11:02:32 -0500 Subject: [PATCH 2/4] Use new api types --- handler/handler.go | 46 ++++++++++++++++++++++++++++++++++------- handler/handler_test.go | 12 +++++++---- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 95e39b5..617a41c 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -2,10 +2,14 @@ package handler import ( "context" + "encoding/json" "fmt" "net/http" "strconv" "strings" + + v0 "github.com/m-lab/autojoin/api/v0" + v2 "github.com/m-lab/locate/api/v2" ) // Server maintains shared state for the server. @@ -35,27 +39,46 @@ func (s *Server) Reload(ctx context.Context) { // Lookup is a handler used to find the nearest IATA given client IP or lat/lon metadata. func (s *Server) Lookup(rw http.ResponseWriter, req *http.Request) { + resp := v0.LookupResponse{} country := rawCountry(req) if country == "" { - rw.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(rw, "could not determine country") + resp.Error = &v2.Error{ + Type: "?country=", + Title: "could not determine country from request", + Status: http.StatusBadRequest, + } + rw.WriteHeader(resp.Error.Status) + writeResponse(rw, resp) return } rlat, rlon := rawLatLon(req) lat, errLat := strconv.ParseFloat(rlat, 64) lon, errLon := strconv.ParseFloat(rlon, 64) if errLat != nil || errLon != nil { - rw.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(rw, "could not determine lat/lon") + resp.Error = &v2.Error{ + Type: "?lat=&lon=", + Title: "could not determine lat/lon from request", + Status: http.StatusBadRequest, + } + rw.WriteHeader(resp.Error.Status) + writeResponse(rw, resp) return } code, err := s.Iata.Lookup(country, lat, lon) if err != nil { - rw.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(rw, "could not determine iata code: %v", err) + resp.Error = &v2.Error{ + Type: "internal error", + Title: "could not determine iata from request", + Status: http.StatusInternalServerError, + } + rw.WriteHeader(resp.Error.Status) + writeResponse(rw, resp) return } - fmt.Fprintf(rw, "%s\n", code) + resp.Lookup = &v0.Lookup{ + IATA: code, + } + writeResponse(rw, resp) } // Register is a handler used by autonodes to register with M-Lab on startup. @@ -104,3 +127,12 @@ func rawLatLon(req *http.Request) (string, string) { // TODO: lookup with request IP. return "", "" } + +func writeResponse(rw http.ResponseWriter, resp interface{}) error { + b, err := json.MarshalIndent(resp, "", " ") + if err != nil { + return err + } + rw.Write(b) + return nil +} diff --git a/handler/handler_test.go b/handler/handler_test.go index 4cd9952..d396d30 100644 --- a/handler/handler_test.go +++ b/handler/handler_test.go @@ -2,11 +2,14 @@ package handler import ( "context" + "encoding/json" "errors" "net/http" "net/http/httptest" - "strings" "testing" + + v0 "github.com/m-lab/autojoin/api/v0" + "github.com/m-lab/go/testingx" ) type fakeIataFinder struct { @@ -102,9 +105,10 @@ func TestServer_Lookup(t *testing.T) { if rw.Code != tt.wantCode { t.Errorf("Lookup() returned wrong code; got %d, want %d", rw.Code, tt.wantCode) } - resp := strings.Trim(rw.Body.String(), "\n") - if rw.Code == http.StatusOK && resp != tt.wantIata { - t.Errorf("Lookup() returned wrong iata; got %s, want %s", resp, tt.wantIata) + resp := &v0.LookupResponse{} + testingx.Must(t, json.Unmarshal(rw.Body.Bytes(), resp), "failed to parse response") + if rw.Code == http.StatusOK && resp.Lookup != nil && resp.Lookup.IATA != tt.wantIata { + t.Errorf("Lookup() returned wrong iata; got %#v, want %s", resp, tt.wantIata) } }) } From 361e79c529e4bc2b460bc258cd99a9f7b0448edd Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Mon, 26 Feb 2024 11:16:15 -0500 Subject: [PATCH 3/4] correct condition --- handler/handler_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/handler_test.go b/handler/handler_test.go index d396d30..3e3256a 100644 --- a/handler/handler_test.go +++ b/handler/handler_test.go @@ -107,7 +107,7 @@ func TestServer_Lookup(t *testing.T) { } resp := &v0.LookupResponse{} testingx.Must(t, json.Unmarshal(rw.Body.Bytes(), resp), "failed to parse response") - if rw.Code == http.StatusOK && resp.Lookup != nil && resp.Lookup.IATA != tt.wantIata { + if rw.Code == http.StatusOK && (resp.Lookup == nil || resp.Lookup.IATA != tt.wantIata) { t.Errorf("Lookup() returned wrong iata; got %#v, want %s", resp, tt.wantIata) } }) From 6d01f5aedfc4846f1628064e313e5ac53d4b4050 Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Mon, 26 Feb 2024 11:23:08 -0500 Subject: [PATCH 4/4] complete coverage --- handler/handler.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 617a41c..9d587bd 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -9,6 +9,7 @@ import ( "strings" v0 "github.com/m-lab/autojoin/api/v0" + "github.com/m-lab/go/rtx" v2 "github.com/m-lab/locate/api/v2" ) @@ -130,9 +131,9 @@ func rawLatLon(req *http.Request) (string, string) { func writeResponse(rw http.ResponseWriter, resp interface{}) error { b, err := json.MarshalIndent(resp, "", " ") - if err != nil { - return err - } + // NOTE: marshal can only fail on incompatible types, like functions. The + // panic will be caught by the http server handler. + rtx.PanicOnError(err, "failed to marshal response") rw.Write(b) return nil }