Skip to content

Commit

Permalink
add get/set Tags to keyvalue, labelmap, annotation; fixes #335
Browse files Browse the repository at this point in the history
  • Loading branch information
DocSavage committed Nov 13, 2019
1 parent b1e9c12 commit 745413f
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 2 deletions.
32 changes: 32 additions & 0 deletions datastore/datainstance.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,8 +426,36 @@ func (d *Data) SetDeleted(deleted bool) {
d.deleted = deleted
}

// SetTagsByJSON takes a JSON object of tags and either appends or replaces the current
// data's tags depending on the replace parameter.
func SetTagsByJSON(d dvid.Data, uuid dvid.UUID, replace bool, in io.ReadCloser) error {
setTags := make(map[string]string)
decoder := json.NewDecoder(in)
if err := decoder.Decode(&setTags); err != nil && err != io.EOF {
return fmt.Errorf("Malformed JSON request in tags setter: %v", err)
}
if replace {
d.SetTags(setTags)
return nil
}
curTags := d.Tags()
for k, v := range setTags {
curTags[k] = v
}
d.SetTags(curTags)
return nil
}

// ---------------------------------------

func (d *Data) MarshalJSONTags() ([]byte, error) {
return json.Marshal(struct {
Tags map[string]string
}{
d.Tags(),
})
}

func (d *Data) MarshalJSON() ([]byte, error) {
// convert sync UUIDs to names so its more understandable.
syncs := []dvid.InstanceName{}
Expand Down Expand Up @@ -657,6 +685,10 @@ func (d *Data) SetSync(syncs dvid.UUIDSet) {
d.syncNames = nil
}

func (d *Data) SetTags(tags map[string]string) {
d.tags = tags
}

func (d *Data) SetKVStore(kvStore dvid.Store) {
d.kvStore = kvStore
}
Expand Down
36 changes: 35 additions & 1 deletion datatype/annotation/annotation.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,23 @@ POST <api URL>/node/<UUID>/<data name>/sync?<options>
Default operation is false.
GET <api URL>/node/<UUID>/<data name>/tags
POST <api URL>/node/<UUID>/<data name>/tags?<options>
GET retrieves JSON of tags for this instance.
POST appends or replaces tags provided in POST body. Expects JSON to be POSTed
with the following format:
{ "tag1": "anything you want", "tag2": "something else" }
To delete tags, pass an empty object with query string "replace=true".
POST Query-string Options:
replace Set to "true" if you want passed tags to replace and not be appended to current tags.
Default operation is false (append).
Note: For the following URL endpoints that return and accept POSTed JSON values, see the JSON format
at end of this documentation.
Expand Down Expand Up @@ -2648,7 +2665,7 @@ func (d *Data) Help() string {

func (d *Data) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Base *datastore.Data
Tags *datastore.Data
Extended Properties
}{
d.Data,
Expand Down Expand Up @@ -2794,6 +2811,23 @@ func (d *Data) ServeHTTP(uuid dvid.UUID, ctx *datastore.VersionedCtx, w http.Res
}
d.cachedBlockSize = nil

case "tags":
if action == "post" {
replace := r.URL.Query().Get("replace") == "true"
if err := datastore.SetTagsByJSON(d, uuid, replace, r.Body); err != nil {
server.BadRequest(w, r, err)
return
}
} else {
jsonBytes, err := d.MarshalJSONTags()
if err != nil {
server.BadRequest(w, r, err)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, string(jsonBytes))
}

case "label":
if action != "get" {
server.BadRequest(w, r, "Only GET action is available on 'label' endpoint.")
Expand Down
33 changes: 33 additions & 0 deletions datatype/keyvalue/keyvalue.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ POST <api URL>/node/<UUID>/<data name>/info
UUID Hexadecimal string with enough characters to uniquely identify a version node.
data name Name of keyvalue data instance.
GET <api URL>/node/<UUID>/<data name>/tags
POST <api URL>/node/<UUID>/<data name>/tags?<options>
GET retrieves JSON of tags for this instance.
POST appends or replaces tags provided in POST body. Expects JSON to be POSTed
with the following format:
{ "tag1": "anything you want", "tag2": "something else" }
To delete tags, pass an empty object with query string "replace=true".
POST Query-string Options:
replace Set to "true" if you want passed tags to replace and not be appended to current tags.
Default operation is false (append).
GET <api URL>/node/<UUID>/<data name>/keys
Returns all keys for this data instance in JSON format:
Expand Down Expand Up @@ -510,6 +526,23 @@ func (d *Data) ServeHTTP(uuid dvid.UUID, ctx *datastore.VersionedCtx, w http.Res
fmt.Fprintf(w, jsonStr)
return

case "tags":
if action == "post" {
replace := r.URL.Query().Get("replace") == "true"
if err := datastore.SetTagsByJSON(d, uuid, replace, r.Body); err != nil {
server.BadRequest(w, r, err)
return
}
} else {
jsonBytes, err := d.MarshalJSONTags()
if err != nil {
server.BadRequest(w, r, err)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, string(jsonBytes))
}

case "keys":
keyList, err := d.GetKeys(ctx)
if err != nil {
Expand Down
34 changes: 33 additions & 1 deletion datatype/labelmap/labelmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,27 @@ POST <api URL>/node/<UUID>/<data name>/sync?<options>
The labelmap data type accepts syncs to labelvol data instances. It also accepts syncs to
labelmap instances for multiscale.
GET Query-string Options:
Query-string Options:
replace Set to "true" if you want passed syncs to replace and not be appended to current syncs.
Default operation is false.
GET <api URL>/node/<UUID>/<data name>/tags
POST <api URL>/node/<UUID>/<data name>/tags?<options>
GET retrieves JSON of tags for this instance.
POST appends or replaces tags provided in POST body. Expects JSON to be POSTed
with the following format:
{ "tag1": "anything you want", "tag2": "something else" }
To delete tags, pass an empty object with query string "replace=true".
POST Query-string Options:
replace Set to "true" if you want passed tags to replace and not be appended to current tags.
Default operation is false (append).
GET <api URL>/node/<UUID>/<data name>/metadata
Retrieves a JSON schema (application/vnd.dvid-nd-data+json) that describes the layout
Expand Down Expand Up @@ -3200,6 +3215,23 @@ func (d *Data) ServeHTTP(uuid dvid.UUID, ctx *datastore.VersionedCtx, w http.Res
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, string(jsonBytes))

case "tags":
if action == "post" {
replace := r.URL.Query().Get("replace") == "true"
if err := datastore.SetTagsByJSON(d, uuid, replace, r.Body); err != nil {
server.BadRequest(w, r, err)
return
}
} else {
jsonBytes, err := d.MarshalJSONTags()
if err != nil {
server.BadRequest(w, r, err)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, string(jsonBytes))
}

case "specificblocks":
// GET <api URL>/node/<UUID>/<data name>/specificblocks?blocks=x,y,z,x,y,z...
queryStrings := r.URL.Query()
Expand Down
2 changes: 2 additions & 0 deletions dvid/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ type DataSetter interface {
// SetSync defines a set of data UUIDs for syncing with this data instance.
// This could be used by higher software layers to implement pub/sub-style syncing.
SetSync(UUIDSet)

SetTags(map[string]string)
}

// Axis enumerates different types of axis (x, y, z, time, etc)
Expand Down
4 changes: 4 additions & 0 deletions storage/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ func (d *testData) SetSync(syncs dvid.UUIDSet) {
d.syncData = syncs
}

func (d *testData) SetTags(tags map[string]string) {
d.tags = tags
}

func (d *testData) Versioned() bool {
return true
}
Expand Down
28 changes: 28 additions & 0 deletions tests_integration/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,34 @@ func TestInstanceTags(t *testing.T) {
t.Errorf("Got bad response: %s\n", string(got))
t.Errorf("Parsed jsonResp: %v\n", jsonResp.Base)
}

payload = bytes.NewBufferString(`{"foo": "something about foo", "bar": "something about bar"}`)
tagURL := fmt.Sprintf("%snode/%s/testkv/tags", server.WebAPIPath, uuid)
server.TestHTTP(t, "POST", tagURL, payload)

got = server.TestHTTP(t, "GET", tagURL, nil)
var jsonResp2 struct {
Tags map[string]string
}
if err := json.Unmarshal(got, &jsonResp2); err != nil {
t.Fatalf("couldn't unmarshal response: %s\n", string(got))
}
if len(jsonResp2.Tags) != 4 || jsonResp2.Tags["foo"] != "something about foo" || jsonResp2.Tags["bar"] != "something about bar" {
t.Fatalf("Got bad response: %s\n", string(got))
}

tagURL += "?replace=true"
payload = bytes.NewBufferString(`{}`)
server.TestHTTP(t, "POST", tagURL, payload)

jsonResp2.Tags = nil
got = server.TestHTTP(t, "GET", tagURL, nil)
if err := json.Unmarshal(got, &jsonResp2); err != nil {
t.Fatalf("couldn't unmarshal response: %s\n", string(got))
}
if len(jsonResp2.Tags) != 0 {
t.Fatalf("Got bad response: %v\n", jsonResp2)
}
}

func TestSyncs(t *testing.T) {
Expand Down

0 comments on commit 745413f

Please sign in to comment.