Skip to content

Commit

Permalink
Allow /api/state/$state to return text/xml/json.
Browse files Browse the repository at this point in the history
This matches our other end-points, and would have saved me a bit
of time this morning building up a list of hosts in the failed-state.

This closes #39.
  • Loading branch information
skx committed Dec 12, 2017
1 parent 3e425cb commit 9d90942
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 9 deletions.
11 changes: 10 additions & 1 deletion API.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,16 @@ API Endpoints
-------------

In addition to the scripting posibilities available with the multi-format
responses there is also a simple end-point which is designed to return a
responses there is also a simple end-point which is designed to return a
list of all the nodes in the given state:

* `GET /api/state/$state`

This will default to JSON, but you can choose JSON, XML, or pain-text, via the
Accept: header or `?accept=application/json` parameter, for example:


$ curl -H Accept:text/plain http://localhost:3001/api/state/unchanged
$ curl -H Accept:application/xml http://localhost:3001/api/state/unchanged
$ curl http://localhost:3001/api/state/unchanged?accept=text/plain
$ curl http://localhost:3001/api/state/unchanged?accept=application/xml
48 changes: 40 additions & 8 deletions cmd_serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ func Exists(name string) bool {
//
// GET /api/state/$state
//
// This only returns JSON, which is perhaps a mistake.
// This only will return plain-text by default, but JSON and XML are both
// possible via the `Accept:` header or `?accept=XX` parameter.
//
func APIState(res http.ResponseWriter, req *http.Request) {

Expand Down Expand Up @@ -109,15 +110,46 @@ func APIState(res http.ResponseWriter, req *http.Request) {
}

//
// Convert the string-array to JSON, and return it.
// What kind of reply should we send?
//
// Accept either a "?accept=XXX" URL-parameter, or
// the Accept HEADER in the HTTP request
//
res.Header().Set("Content-Type", "application/json")
accept := req.FormValue("accept")
if len(accept) < 1 {
accept = req.Header.Get("Accept")
}

switch accept {
case "text/plain":
res.Header().Set("Content-Type", "text/plain")

for _, o := range result {
fmt.Fprintf(res, "%s\n", o)
}
case "application/xml":
x, err := xml.MarshalIndent(result, "", " ")
if err != nil {
status = http.StatusInternalServerError
return
}

res.Header().Set("Content-Type", "application/xml")
res.Write(x)
default:

//
// Convert the string-array to JSON, and return it.
//
res.Header().Set("Content-Type", "application/json")

if len(result) > 0 {
out, _ := json.Marshal(result)
fmt.Fprintf(res, "%s", out)
} else {
fmt.Fprintf(res, "[]")
}

if len(result) > 0 {
out, _ := json.Marshal(result)
fmt.Fprintf(res, "%s", out)
} else {
fmt.Fprintf(res, "[]")
}

}
Expand Down
80 changes: 80 additions & 0 deletions cmd_serve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,86 @@ func TestKnownAPIState(t *testing.T) {

}

//
// API state should accept XML, JSON, and plain-text
//
func TestAPITypes(t *testing.T) {

// Create a fake database
FakeDB()

// Add some data.
addFakeNodes()

// Wire up the router.
r := mux.NewRouter()
r.HandleFunc("/api/state/{state}", APIState).Methods("GET")
r.HandleFunc("/api/state/{state}/", APIState).Methods("GET")

// Get the test-server
ts := httptest.NewServer(r)
defer ts.Close()

//
// We'll make one test for each known-state
//
type TestCase struct {
Type string
Response string
}

tests := []TestCase{
{"application/json", "[\"foo.example.com\"]"},
{"application/xml", "<string>foo.example.com</string>"},
{"text/plain", "foo.example.com\n"},
{"", "[\"foo.example.com\"]"},
}

//
// Run each one.
//
for _, test := range tests {

//
// Make the request
//
url := ts.URL + "/api/state/changed?accept=" + test.Type

resp, err := http.Get(url)
if err != nil {
t.Fatal(err)
}

//
// Get the body
//
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)

if err != nil {
t.Errorf("Failed to read response-body %v\n", err)
}

content := fmt.Sprintf("%s", body)

if status := resp.StatusCode; status != http.StatusOK {
t.Errorf("Unexpected status-code: %v", status)
}
if content != test.Response {
t.Fatalf("Unexpected body for %s: '%s'", test.Type, body)
}
}

//
// Cleanup here because otherwise later tests will
// see an active/valid DB-handle.
//
db.Close()
db = nil
os.RemoveAll(path)

}

//
// Searching must be done via a POST.
//
Expand Down

0 comments on commit 9d90942

Please sign in to comment.