Skip to content

Commit

Permalink
http: expose node delete api; resolves #622
Browse files Browse the repository at this point in the history
  • Loading branch information
dennwc committed Dec 24, 2017
1 parent 41bf496 commit 8aebfe9
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 8 deletions.
3 changes: 2 additions & 1 deletion docs/HTTP.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# HTTP Methods

This file cover deprecated v1 HTTP API. All the methods of v2 HTTP API is described in OpenAPI/Swagger [spec](./api/swagger.yml) and can be viewed by passing `https://raw.githubusercontent.com/cayleygraph/cayley/master/docs/api/swagger.yml` address to [Swagger UI demo](http://petstore.swagger.io/).
This file covers deprecated v1 HTTP API. All the methods of v2 HTTP API is described in OpenAPI/Swagger [spec](./api/swagger.yml)
and can be viewed by importing `https://raw.githubusercontent.com/cayleygraph/cayley/master/docs/api/swagger.yml` URL into [Swagger Editor](https://editor.swagger.io/) or [Swagger UI demo](http://petstore.swagger.io/).

## Gephi

Expand Down
70 changes: 68 additions & 2 deletions docs/api/swagger.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: "3.0.0"
info:
description: ""
version: "2.0.0"
version: "2.1.0"
title: "Cayley API"
license:
name: "Apache 2.0"
Expand Down Expand Up @@ -35,20 +35,29 @@ paths:
type: "object"
properties:
id:
description: "unique name of the format"
type: "string"
read:
description: "format is supported for loading quads"
type: "boolean"
write:
description: "format is supported for exporting quads"
type: "boolean"
nodes:
description: "format can be used to describe nodes"
type: "boolean"
ext:
description: "typical file extensions for this format"
type: "array"
items:
type: "string"
mime:
description: "typical content types for this format"
type: "array"
items:
type: "string"
binary:
description: "format uses binary encoding"
type: "boolean"
default:
description: "Unexpected error"
Expand Down Expand Up @@ -158,6 +167,53 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/v2/node/delete:
post:
tags:
- "data"
summary: "Removes a node add all associated quads"
description: ""
operationId: "deleteNode"
requestBody:
description: "File in one of formats specified in Content-Type."
required: true
content:
'application/n-quads':
schema:
$ref: '#/components/schemas/NQuadsNode'
'application/json':
schema:
$ref: '#/components/schemas/JsonNode'
'application/x-protobuf':
schema:
$ref: '#/components/schemas/PNode'
parameters:
- name: "format"
in: "query"
description: "Data decoder to use for request. Overrides Content-Type."
required: false
schema:
type: "string"
responses:
200:
description: "delete successful"
content:
application/json:
schema:
type: "object"
properties:
result:
type: "string"
description: "legacy success message"
count:
type: "integer"
description: "number of nodes deleted"
default:
description: "Unexpected error"
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/api/v2/delete:
post:
tags:
Expand Down Expand Up @@ -217,7 +273,7 @@ paths:
- "queries"
summary: "Query the graph"
description: ""
operationId: "readQuads"
operationId: "query"
parameters:
- name: "lang"
in: "query"
Expand Down Expand Up @@ -319,6 +375,10 @@ components:
<predicates> <are> <status> .
<emily> <status> "smart_person" <smart_graph> .
<greg> <status> "smart_person" <smart_graph> .
NQuadsNode:
type: "string"
format: "binary"
example: "<alice>"
JSONLD:
type: "string"
format: "binary"
Expand Down Expand Up @@ -356,6 +416,8 @@ components:
type: "string"
label:
type: "string"
JsonNode:
type: "string"
JsonQuadsStream:
type: "string"
format: "binary"
Expand All @@ -364,6 +426,10 @@ components:
type: "string"
format: "binary"
description: "Cayley-specific binary encoding of quads based on protobuf"
PNode:
type: "string"
format: "binary"
description: "Cayley-specific binary encoding of node value based on protobuf"
Error:
type: "object"
properties:
Expand Down
4 changes: 4 additions & 0 deletions quad/formats.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ type Format struct {
Writer func(io.Writer) WriteCloser
// Binary is set to true if format is not human-readable.
Binary bool
// MarshalValue encodes one value in specific a format.
MarshalValue func(v Value) ([]byte, error)
// UnmarshalValue decodes a value from specific format.
UnmarshalValue func(b []byte) (Value, error)
}

var (
Expand Down
12 changes: 12 additions & 0 deletions quad/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ func init() {
Mime: []string{"application/json"},
Writer: func(w io.Writer) quad.WriteCloser { return NewWriter(w) },
Reader: func(r io.Reader) quad.ReadCloser { return NewReader(r) },
MarshalValue: func(v quad.Value) ([]byte, error) {
return json.Marshal(quad.ToString(v))
},
UnmarshalValue: func(b []byte) (quad.Value, error) {
var s *string
if err := json.Unmarshal(b, &s); err != nil {
return nil, err
} else if s == nil {
return nil, nil
}
return quad.StringToValue(*s), nil
},
})
quad.RegisterFormat(quad.Format{
Name: "json-stream",
Expand Down
27 changes: 27 additions & 0 deletions quad/json/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"testing"

"github.com/cayleygraph/cayley/quad"
"github.com/stretchr/testify/require"
)

var readTests = []struct {
Expand Down Expand Up @@ -147,3 +148,29 @@ func TestWriteJSON(t *testing.T) {
}
}
}

func TestValueEncoding(t *testing.T) {
vals := []quad.Value{
quad.String("some val"),
quad.IRI("iri"),
quad.BNode("bnode"),
quad.TypedString{Value: "10", Type: "int"},
quad.LangString{Value: "val", Lang: "en"},
}
enc := []string{
`"some val"`,
`"\u003ciri\u003e"`,
`"_:bnode"`,
`"\"10\"^^\u003cint\u003e"`,
`"\"val\"@en"`,
}
f := quad.FormatByName("json")
for i, v := range vals {
data, err := f.MarshalValue(v)
require.NoError(t, err)
require.Equal(t, enc[i], string(data), string(data))
v2, err := f.UnmarshalValue(data)
require.NoError(t, err)
require.Equal(t, v, v2)
}
}
21 changes: 21 additions & 0 deletions quad/nquads/nquads.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,27 @@ func init() {
return NewReader(r, DecodeRaw)
},
Writer: func(w io.Writer) quad.WriteCloser { return NewWriter(w) },
MarshalValue: func(v quad.Value) ([]byte, error) {
if v == nil {
return nil, nil
}
return []byte(v.String()), nil
},
UnmarshalValue: func(b []byte) (quad.Value, error) {
// TODO: proper parser for a single value
r := NewReader(bytes.NewReader(bytes.Join([][]byte{
[]byte("<s> <p> "),
b,
[]byte(" <l> .\n"),
}, nil)), false)
q, err := r.ReadQuad()
if err == io.EOF {
return nil, quad.ErrInvalid
} else if err != nil {
return nil, err
}
return q.Object, nil
},
})
}

Expand Down
27 changes: 27 additions & 0 deletions quad/nquads/typed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"time"

"github.com/cayleygraph/cayley/quad"
"github.com/stretchr/testify/require"
)

var testNQuads = []struct {
Expand Down Expand Up @@ -841,3 +842,29 @@ func BenchmarkParser(b *testing.B) {
result, _ = Parse("<http://example/s> <http://example/p> \"object of some real\\tlength\"@en . # comment")
}
}

func TestValueEncoding(t *testing.T) {
vals := []quad.Value{
quad.String("some val"),
quad.IRI("iri"),
quad.BNode("bnode"),
quad.TypedString{Value: "10", Type: "int"},
quad.LangString{Value: "val", Lang: "en"},
}
enc := []string{
`"some val"`,
`<iri>`,
`_:bnode`,
`"10"^^<int>`,
`"val"@en`,
}
f := quad.FormatByName("nquads")
for i, v := range vals {
data, err := f.MarshalValue(v)
require.NoError(t, err)
require.Equal(t, enc[i], string(data), string(data))
v2, err := f.UnmarshalValue(data)
require.NoError(t, err)
require.Equal(t, v, v2)
}
}
10 changes: 6 additions & 4 deletions quad/pquads/pquads.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ const ContentType = "application/x-protobuf"
func init() {
quad.RegisterFormat(quad.Format{
Name: "pquads", Binary: true,
Ext: []string{".pq"},
Mime: []string{ContentType, "application/octet-stream"},
Writer: func(w io.Writer) quad.WriteCloser { return NewWriter(w, nil) },
Reader: func(r io.Reader) quad.ReadCloser { return NewReader(r, DefaultMaxSize) },
Ext: []string{".pq"},
Mime: []string{ContentType, "application/octet-stream"},
Writer: func(w io.Writer) quad.WriteCloser { return NewWriter(w, nil) },
Reader: func(r io.Reader) quad.ReadCloser { return NewReader(r, DefaultMaxSize) },
MarshalValue: MarshalValue,
UnmarshalValue: UnmarshalValue,
})
}

Expand Down
4 changes: 4 additions & 0 deletions quad/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ func StringToValue(v string) Value {
return String(v[1 : len(v)-1])
} else if v[:2] == "_:" {
return BNode(v[2:])
} else if i := strings.Index(v, `"^^<`); i > 0 && v[0] == '"' && v[len(v)-1] == '>' {
return TypedString{Value: String(v[1:i]), Type: IRI(v[i+4 : len(v)-1])}
} else if i := strings.Index(v, `"@`); i > 0 && v[0] == '"' && v[len(v)-1] != '"' {
return LangString{Value: String(v[1:i]), Lang: v[i+2:]}
}
}
return String(v)
Expand Down
49 changes: 48 additions & 1 deletion server/http/api_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ func (api *APIv2) RegisterDataOn(r *httprouter.Router, wrappers ...HandlerWrappe
if !api.ro {
r.POST("/api/v2/write", wrap(api.ServeWrite, wrappers))
r.POST("/api/v2/delete", wrap(api.ServeDelete, wrappers))
r.POST("/api/v2/node/delete", wrap(api.ServeNodeDelete, wrappers))
}
r.POST("/api/v2/read", wrap(api.ServeRead, wrappers))
r.GET("/api/v2/read", wrap(api.ServeRead, wrappers))
Expand Down Expand Up @@ -216,7 +217,7 @@ func (api *APIv2) ServeDelete(w http.ResponseWriter, r *http.Request) {
}
format := getFormat(r, "", hdrContentType)
if format == nil || format.Reader == nil {
jsonResponse(w, http.StatusBadRequest, fmt.Errorf("format is not supported for reading data"))
jsonResponse(w, http.StatusBadRequest, fmt.Errorf("format is not supported for reading quads"))
return
}
rd, err := readerFrom(r, hdrContentEncoding)
Expand All @@ -243,6 +244,50 @@ func (api *APIv2) ServeDelete(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"result": "Successfully deleted %d quads.", "count": %d}`+"\n", n, n)
}

func (api *APIv2) ServeNodeDelete(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if api.ro {
jsonResponse(w, http.StatusForbidden, errors.New("database is read-only"))
return
}
format := getFormat(r, "", hdrContentType)
if format == nil || format.UnmarshalValue == nil {
jsonResponse(w, http.StatusBadRequest, fmt.Errorf("format is not supported for reading nodes"))
return
}
const limit = 128*1024 + 1
rd := io.LimitReader(r.Body, limit)
data, err := ioutil.ReadAll(rd)
if err != nil {
jsonResponse(w, http.StatusBadRequest, err)
return
} else if len(data) == limit {
jsonResponse(w, http.StatusBadRequest, fmt.Errorf("request data is too large"))
return
}
v, err := format.UnmarshalValue(data)
if err != nil {
jsonResponse(w, http.StatusBadRequest, err)
return
} else if v == nil {
jsonResponse(w, http.StatusBadRequest, fmt.Errorf("cannot remove nil value"))
return
}
h, err := api.handleForRequest(r)
if err != nil {
jsonResponse(w, http.StatusBadRequest, err)
return
}
err = h.RemoveNode(v)
if err != nil {
jsonResponse(w, http.StatusInternalServerError, err)
return
}
w.Header().Set(hdrContentType, contentTypeJSON)
const n = 1
fmt.Fprintf(w, `{"result": "Successfully deleted %d nodes.", "count": %d}`+"\n", n, n)
}

type checkWriter struct {
w io.Writer
written bool
Expand Down Expand Up @@ -296,6 +341,7 @@ func (api *APIv2) ServeFormats(w http.ResponseWriter, r *http.Request) {
Id string `json:"id"`
Read bool `json:"read,omitempty"`
Write bool `json:"write,omitempty"`
Nodes bool `json:"nodes,omitempty"`
Ext []string `json:"ext,omitempty"`
Mime []string `json:"mime,omitempty"`
Binary bool `json:"binary,omitempty"`
Expand All @@ -307,6 +353,7 @@ func (api *APIv2) ServeFormats(w http.ResponseWriter, r *http.Request) {
Id: f.Name,
Ext: f.Ext, Mime: f.Mime,
Read: f.Reader != nil, Write: f.Writer != nil,
Nodes: f.UnmarshalValue != nil,
Binary: f.Binary,
})
}
Expand Down

0 comments on commit 8aebfe9

Please sign in to comment.