Skip to content
This repository has been archived by the owner on Apr 4, 2023. It is now read-only.

Commit

Permalink
Merge pull request #33 from linksmart/patch-support
Browse files Browse the repository at this point in the history
Add patch support
  • Loading branch information
farshidtz authored Dec 29, 2020
2 parents 62b6c8a + e84006f commit 5df8721
Show file tree
Hide file tree
Showing 24 changed files with 2,356 additions and 44 deletions.
90 changes: 63 additions & 27 deletions apidoc/openapi-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ openapi: 3.0.0
# - url: http://localhost:8081

info:
version: "1.0.0-beta.13"
version: "1.0.0-beta.18"
title: LinkSmart Thing Directory
description: API documetnation of the [LinkSmart Thing Directory](https://github.com/linksmart/thing-directory)
license:
Expand Down Expand Up @@ -136,6 +136,42 @@ paths:
$ref: '#/components/examples/ThingDescriptionWithID'
description: The Thing Description object
required: true
patch:
tags:
- td
summary: Patch a Thing Description
description: The patch document must be based on RFC7396 JSON Merge Patch
parameters:
- name: id
in: path
description: ID of the Thing Description
example: "urn:example:1234"
required: true
schema:
type: string
responses:
'200':
description: Thing Description patched successfully
'400':
$ref: '#/components/responses/RespBadRequest'
'401':
$ref: '#/components/responses/RespUnauthorized'
'403':
$ref: '#/components/responses/RespForbidden'
'409':
$ref: '#/components/responses/RespConflict'
'500':
$ref: '#/components/responses/RespInternalServerError'
requestBody:
content:
application/merge-patch+json:
schema:
type: object
examples:
ThingDescription:
$ref: '#/components/examples/ThingDescriptionWithID'
description: The Thing Description object
required: true
get:
tags:
- td
Expand Down Expand Up @@ -338,11 +374,11 @@ components:
"properties": {
"status": {
"forms": [
{
"op": ["readproperty"],
"href": "https://example.com/status",
"contentType": "text/html"
}
{
"op": ["readproperty"],
"href": "https://example.com/status",
"contentType": "text/html"
}
]
}
},
Expand All @@ -360,11 +396,11 @@ components:
"properties": {
"status": {
"forms": [
{
"op": ["readproperty"],
"href": "https://example.com/status",
"contentType": "text/html"
}
{
"op": ["readproperty"],
"href": "https://example.com/status",
"contentType": "text/html"
}
]
}
},
Expand All @@ -379,25 +415,25 @@ components:
"@context": "https://linksmart.eu/thing-directory/context.jsonld",
"@type": "Catalog",
"items":[
{
"@context": "https://www.w3.org/2019/wot/td/v1",
"id": "urn:example:1234",
"title": "ExampleSensor",
"properties": {
"status": {
"forms": [
{
"op": ["readproperty"],
"href": "https://example.com/status",
"contentType": "text/html"
{
"@context": "https://www.w3.org/2019/wot/td/v1",
"id": "urn:example:1234",
"title": "ExampleSensor",
"properties": {
"status": {
"forms": [
{
"op": ["readproperty"],
"href": "https://example.com/status",
"contentType": "text/html"
}
]
}
]
},
"security": ["nosec_sc"],
"securityDefinitions": {"nosec_sc":{"scheme":"nosec"}
}
},
"security": ["nosec_sc"],
"securityDefinitions": {"nosec_sc":{"scheme":"nosec"}
}
}
],
"page": 1,
"perPage": 100,
Expand Down
1 change: 1 addition & 0 deletions catalog/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type CatalogController interface {
add(d ThingDescription) (string, error)
get(id string) (ThingDescription, error)
update(id string, d ThingDescription) error
patch(id string, d ThingDescription) error
delete(id string) error
list(page, perPage int) ([]ThingDescription, int, error)
filterJSONPath(path string, page, perPage int) ([]interface{}, int, error)
Expand Down
47 changes: 47 additions & 0 deletions catalog/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

xpath "github.com/antchfx/jsonquery"
jsonpath "github.com/bhmj/jsonslice"
jsonpatch "github.com/evanphx/json-patch/v5"
"github.com/linksmart/service-catalog/v3/utils"
uuid "github.com/satori/go.uuid"
)
Expand Down Expand Up @@ -80,6 +81,50 @@ func (c *Controller) update(id string, td ThingDescription) error {
return nil
}

// TODO: Improve patch by reducing the number of (de-)serializations
func (c *Controller) patch(id string, td ThingDescription) error {
oldTD, err := c.storage.get(id)
if err != nil {
return err
}

// serialize to json for mergepatch input
oldBytes, err := json.Marshal(oldTD)
if err != nil {
return err
}
patchBytes, err := json.Marshal(td)
if err != nil {
return err
}
//fmt.Printf("%s", patchBytes)

newBytes, err := jsonpatch.MergePatch(oldBytes, patchBytes)
if err != nil {
return err
}
oldBytes, patchBytes = nil, nil

td = ThingDescription{}
err = json.Unmarshal(newBytes, &td)
if err != nil {
return err
}

if err := validateThingDescription(td); err != nil {
return &BadRequestError{err.Error()}
}

td[_modified] = time.Now().UTC()

err = c.storage.update(id, td)
if err != nil {
return err
}

return nil
}

func (c *Controller) delete(id string) error {
err := c.storage.delete(id)
if err != nil {
Expand Down Expand Up @@ -114,6 +159,7 @@ func (c *Controller) listAll() ([]ThingDescription, int, error) {
}
}

// TODO: Improve filterJSONPath by reducing the number of (de-)serializations
func (c *Controller) filterJSONPath(path string, page, perPage int) ([]interface{}, int, error) {
var results []interface{}

Expand Down Expand Up @@ -155,6 +201,7 @@ func (c *Controller) filterJSONPath(path string, page, perPage int) ([]interface
return results[offset : offset+limit], len(results), nil
}

// TODO: Improve filterXPath by reducing the number of (de-)serializations
func (c *Controller) filterXPath(path string, page, perPage int) ([]interface{}, int, error) {
var results []interface{}

Expand Down
39 changes: 38 additions & 1 deletion catalog/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,44 @@ func (a *HTTPAPI) Put(w http.ResponseWriter, req *http.Request) {

// Patch updates parts or all of an existing item (Response: StatusOK)
func (a *HTTPAPI) Patch(w http.ResponseWriter, req *http.Request) {
ErrorResponse(w, http.StatusNotImplemented, "PATCH method is not implemented")
params := mux.Vars(req)

body, err := ioutil.ReadAll(req.Body)
req.Body.Close()
if err != nil {
ErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

var td ThingDescription
if err := json.Unmarshal(body, &td); err != nil {
ErrorResponse(w, http.StatusBadRequest, "Error processing the request:", err.Error())
return
}

if id, ok := td[_id].(string); ok && id == "" {
if params["id"] != td[_id] {
ErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("Resource id in path (%s) does not match the id in body (%s)", params["id"], td[_id]))
return
}
}

err = a.controller.patch(params["id"], td)
if err != nil {
switch err.(type) {
case *NotFoundError:
ErrorResponse(w, http.StatusNotFound, "Invalid registration:", err.Error())
return
case *BadRequestError:
ErrorResponse(w, http.StatusBadRequest, "Invalid registration:", err.Error())
return
default:
ErrorResponse(w, http.StatusInternalServerError, "Error updating the registration:", err.Error())
return
}
}

w.WriteHeader(http.StatusOK)
}

// Get handler get one item
Expand Down
Loading

0 comments on commit 5df8721

Please sign in to comment.