[#1] Add search route

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2022-04-18 11:30:34 +03:00 committed by Alex Vanin
parent c7c570fd10
commit dc1926f9c6
18 changed files with 1837 additions and 126 deletions

View file

@ -66,6 +66,7 @@ func (a *API) Configure(api *operations.NeofsRestGwAPI) http.Handler {
api.PutObjectHandler = operations.PutObjectHandlerFunc(a.PutObjects)
api.GetObjectInfoHandler = operations.GetObjectInfoHandlerFunc(a.GetObjectInfo)
api.DeleteObjectHandler = operations.DeleteObjectHandlerFunc(a.DeleteObject)
api.SearchObjectsHandler = operations.SearchObjectsHandlerFunc(a.SearchObjects)
api.PutContainerHandler = operations.PutContainerHandlerFunc(a.PutContainers)
api.GetContainerHandler = operations.GetContainerHandlerFunc(a.GetContainer)

View file

@ -1,6 +1,7 @@
package handlers
import (
"context"
"crypto/ecdsa"
"encoding/base64"
"fmt"
@ -67,7 +68,7 @@ func (a *API) PutObjects(params operations.PutObjectParams, principal *models.Pr
return errorResponse.WithPayload(NewError(err))
}
var resp operations.PutObjectOKBody
var resp models.Address
resp.ContainerID = params.Object.ContainerID
resp.ObjectID = NewString(objID.String())
@ -144,6 +145,109 @@ func (a *API) DeleteObject(params operations.DeleteObjectParams, principal *mode
return operations.NewDeleteObjectNoContent()
}
// SearchObjects handler that removes object from NeoFS.
func (a *API) SearchObjects(params operations.SearchObjectsParams, principal *models.Principal) middleware.Responder {
errorResponse := operations.NewSearchObjectsBadRequest()
ctx := params.HTTPRequest.Context()
var cnrID cid.ID
if err := cnrID.Parse(params.ContainerID); err != nil {
a.log.Error("invalid container id", zap.Error(err))
return errorResponse.WithPayload("invalid container id")
}
btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect)
if err != nil {
a.log.Error("failed to get bearer token", zap.Error(err))
return errorResponse.WithPayload(NewError(err))
}
filters, err := ToNativeFilters(params.SearchFilters)
if err != nil {
a.log.Error("failed to transform to native", zap.Error(err))
return errorResponse.WithPayload(NewError(err))
}
var prm pool.PrmObjectSearch
prm.SetContainerID(cnrID)
prm.UseBearer(btoken)
prm.SetFilters(filters)
resSearch, err := a.pool.SearchObjects(ctx, prm)
if err != nil {
a.log.Error("failed to search objects", zap.Error(err))
return errorResponse.WithPayload(NewError(err))
}
offset := int(*params.Offset)
size := int(*params.Limit)
var iterateErr error
var obj *models.ObjectBaseInfo
var objects []*models.ObjectBaseInfo
i := 0
err = resSearch.Iterate(func(id oid.ID) bool {
if i < offset {
i++
return false
}
if obj, iterateErr = headObjectBaseInfo(ctx, a.pool, &cnrID, &id, btoken); iterateErr != nil {
return true
}
objects = append(objects, obj)
return len(objects) == size
})
if err == nil {
err = iterateErr
}
if err != nil {
a.log.Error("failed to search objects", zap.Error(err))
return errorResponse.WithPayload(NewError(err))
}
list := &models.ObjectList{
Size: NewInteger(int64(len(objects))),
Objects: objects,
}
return operations.NewSearchObjectsOK().WithPayload(list)
}
func headObjectBaseInfo(ctx context.Context, p *pool.Pool, cnrID *cid.ID, objID *oid.ID, btoken *token.BearerToken) (*models.ObjectBaseInfo, error) {
addr := address.NewAddress()
addr.SetContainerID(cnrID)
addr.SetObjectID(objID)
var prm pool.PrmObjectHead
prm.SetAddress(*addr)
prm.UseBearer(btoken)
objInfo, err := p.HeadObject(ctx, prm)
if err != nil {
return nil, err
}
resp := &models.ObjectBaseInfo{
Address: &models.Address{
ContainerID: NewString(cnrID.String()),
ObjectID: NewString(objID.String()),
},
}
for _, attr := range objInfo.Attributes() {
if attr.Key() == object.AttributeFileName {
resp.Name = attr.Value()
break
}
}
return resp, nil
}
func parseAddress(containerID, objectID string) (*address.Address, error) {
var cnrID cid.ID
if err := cnrID.Parse(containerID); err != nil {

View file

@ -3,6 +3,7 @@ package handlers
import (
"encoding/hex"
"fmt"
"github.com/nspcc-dev/neofs-sdk-go/object"
sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
@ -391,3 +392,40 @@ func ToNativeTable(records []*models.Record) (*eacl.Table, error) {
return table, nil
}
// ToNativeMatchFilter converts models.SearchMatch to object.SearchMatchType.
func ToNativeMatchFilter(s *models.SearchMatch) (object.SearchMatchType, error) {
if s == nil {
return object.MatchUnknown, fmt.Errorf("unsupported empty verb type")
}
switch *s {
case models.SearchMatchMatchStringEqual:
return object.MatchStringEqual, nil
case models.SearchMatchMatchStringNotEqual:
return object.MatchStringNotEqual, nil
case models.SearchMatchMatchNotPresent:
return object.MatchNotPresent, nil
case models.SearchMatchMatchCommonPrefix:
return object.MatchCommonPrefix, nil
default:
return object.MatchUnknown, fmt.Errorf("unsupported search match: '%s'", *s)
}
}
// ToNativeFilters converts models.SearchFilters to object.SearchFilters.
func ToNativeFilters(fs *models.SearchFilters) (object.SearchFilters, error) {
filters := object.NewSearchFilters()
filters.AddRootFilter()
for _, f := range fs.Filters {
matchFilter, err := ToNativeMatchFilter(f.Match)
if err != nil {
return nil, err
}
filters.AddFilter(*f.Key, *f.Value, matchFilter)
}
return filters, nil
}