1
Fork 0

Use JSON arrays instead of plain text in API.

Suggested by Dianne Skoll.
This commit is contained in:
Juliusz Chroboczek 2024-05-03 18:54:22 +02:00
parent aa35408dba
commit a12331ee09
4 changed files with 42 additions and 61 deletions

View File

@ -43,8 +43,8 @@ allowed methods are HEAD and GET.
/galene-api/v0/.groups/
Returns a list of groups, as plain text, one per line. The only allowed
methods are HEAD and GET.
Returns a list of groups, as a JSON array. The only allowed methods are
HEAD and GET.
### Group definition
@ -75,8 +75,8 @@ accepted content-type is `application/jwk-set+json`.
/galene-api/v0/.groups/groupname/.users/
Returns a list of users, as plain text, one per line. The only allowed
methods are HEAD and GET.
Returns a list of users, as a JSON array. The only allowed methods are
HEAD and GET.
### User definition
@ -101,9 +101,9 @@ POST.
/galene-api/v0/.groups/groupname/.users/username/.tokens/
GET returns the list of stateful tokens, as plain text, one token per
line. POST creates a new token, and returns its name in the `Location`
header. Allowed methods are HEAD, GET and POST.
GET returns the list of stateful tokens, as a JSON array. POST creates
a new token, and returns its name in the `Location` header. Allowed
methods are HEAD, GET and POST.
### Stateful token

View File

@ -49,12 +49,11 @@ async function listObjects(url) {
let r = await fetch(url);
if(!r.ok)
throw httpError(r);
let strings = (await r.text()).split('\n');
if(strings[strings.length - 1] === '') {
strings.pop();
}
return strings;
}
let data = await r.json();
if(!(data instanceof Array))
throw new Error("Server didn't return array");
return data;
}
/**
* createObject makes a PUT request to url with JSON data.

View File

@ -7,7 +7,6 @@ import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
@ -118,13 +117,12 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) {
httpError(w, err)
return
}
w.Header().Set("content-type", "text/plain; charset=utf-8")
w.Header().Set("content-type", "application/json")
if r.Method == "HEAD" {
return
}
for _, g := range groups {
fmt.Fprintln(w, g)
}
e := json.NewEncoder(w)
e.Encode(groups)
return
}
@ -257,7 +255,7 @@ func usersHandler(w http.ResponseWriter, r *http.Request, g, pth string) {
httpError(w, err)
return
}
w.Header().Set("content-type", "text/plain; charset=utf-8")
w.Header().Set("content-type", "application/json")
w.Header().Set("etag", etag)
done := checkPreconditions(w, r, etag)
if done {
@ -266,9 +264,8 @@ func usersHandler(w http.ResponseWriter, r *http.Request, g, pth string) {
if r.Method == "HEAD" {
return
}
for _, u := range users {
fmt.Fprintln(w, u)
}
e := json.NewEncoder(w)
e.Encode(users)
return
}
@ -545,17 +542,19 @@ func tokensHandler(w http.ResponseWriter, r *http.Request, g, pth string) {
httpError(w, err)
return
}
w.Header().Set("content-type",
"text/plain; charset=utf-8")
w.Header().Set("content-type", "application/json")
if etag != "" {
w.Header().Set("etag", etag)
}
if r.Method == "HEAD" {
return
}
for _, t := range tokens {
fmt.Fprintln(w, t.Token)
toknames := make([]string, len(tokens))
for i, t := range tokens {
toknames[i] = t.Token
}
e := json.NewEncoder(w)
e.Encode(toknames)
return
} else if r.Method == "POST" {
ctype := parseContentType(r.Header.Get("Content-Type"))
@ -586,8 +585,6 @@ func tokensHandler(w http.ResponseWriter, r *http.Request, g, pth string) {
httpError(w, err)
return
}
w.Header().Set("content-type",
"text/plain; charset=utf-8")
w.Header().Set("location", t.Token)
w.WriteHeader(http.StatusCreated)
return

View File

@ -3,7 +3,6 @@ package webserver
import (
"errors"
"fmt"
"io"
"os"
"strings"
"sync"
@ -91,23 +90,6 @@ func TestApi(t *testing.T) {
return client.Do(req)
}
getString := func(path string) (string, error) {
resp, err := do("GET", path, "", "", "", "")
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("Status is %v", resp.StatusCode)
}
ctype := parseContentType(resp.Header.Get("Content-Type"))
if !strings.EqualFold(ctype, "text/plain") {
return "", errors.New("Unexpected Content-Type")
}
b, err := io.ReadAll(resp.Body)
return string(b), err
}
getJSON := func(path string, value any) error {
resp, err := do("GET", path, "", "", "", "")
if err != nil {
@ -119,14 +101,15 @@ func TestApi(t *testing.T) {
}
ctype := parseContentType(resp.Header.Get("Content-Type"))
if !strings.EqualFold(ctype, "application/json") {
return errors.New("Unexpected")
return errors.New("Unexpected content-type")
}
d := json.NewDecoder(resp.Body)
return d.Decode(value)
}
s, err := getString("/galene-api/v0/.groups/")
if err != nil || s != "" {
var groups []string
err = getJSON("/galene-api/v0/.groups/", &groups)
if err != nil || len(groups) != 0 {
t.Errorf("Get groups: %v", err)
}
@ -171,9 +154,9 @@ func TestApi(t *testing.T) {
t.Errorf("Delete group (bad ETag): %v %v", err, resp.StatusCode)
}
s, err = getString("/galene-api/v0/.groups/")
if err != nil || s != "test\n" {
t.Errorf("Get groups: %v %#v", err, s)
err = getJSON("/galene-api/v0/.groups/", &groups)
if err != nil || len(groups) != 1 || groups[0] != "test" {
t.Errorf("Get groups: %v %v", err, groups)
}
resp, err = do("PUT", "/galene-api/v0/.groups/test/.fallback-users",
@ -193,8 +176,8 @@ func TestApi(t *testing.T) {
t.Errorf("Set key: %v %v", err, resp.StatusCode)
}
s, err = getString("/galene-api/v0/.groups/test/.users/")
if err != nil || s != "" {
err = getJSON("/galene-api/v0/.groups/test/.users/", &groups)
if err != nil || len(groups) != 0 {
t.Errorf("Get users: %v", err)
}
@ -213,9 +196,10 @@ func TestApi(t *testing.T) {
t.Errorf("Create user: %v %v", err, resp.StatusCode)
}
s, err = getString("/galene-api/v0/.groups/test/.users/")
if err != nil || s != "jch\n" {
t.Errorf("Get users: %v", err)
var users []string
err = getJSON("/galene-api/v0/.groups/test/.users/", &users)
if err != nil || len(users) != 1 || users[0] != "jch" {
t.Errorf("Get users: %v %v", err, users)
}
resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch",
@ -296,11 +280,12 @@ func TestApi(t *testing.T) {
t.Errorf("Create token: %v %v", err, resp.StatusCode)
}
tokname, err := getString("/galene-api/v0/.groups/test/.tokens/")
if err != nil {
t.Errorf("Get tokens: %v", err)
var toknames []string
err = getJSON("/galene-api/v0/.groups/test/.tokens/", &toknames)
if err != nil || len(toknames) != 1 {
t.Errorf("Get tokens: %v %v", err, toknames)
}
tokname = tokname[:len(tokname)-1]
tokname := toknames[0]
tokens, etag, err := token.List("test")
if err != nil || len(tokens) != 1 || tokens[0].Token != tokname {