forked from TrueCloudLab/frostfs-rest-gw
[#1] Use body to provide object attribute
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
3727f5561d
commit
06060348ae
8 changed files with 285 additions and 164 deletions
|
@ -227,13 +227,17 @@ func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnr
|
|||
attrKey: attrValue,
|
||||
}
|
||||
|
||||
req := operations.PutObjectBody{
|
||||
req := &models.ObjectUpload{
|
||||
ContainerID: handlers.NewString(cnrID.String()),
|
||||
FileName: handlers.NewString("newFile.txt"),
|
||||
Payload: base64.StdEncoding.EncodeToString([]byte(content)),
|
||||
Attributes: []*models.Attribute{{
|
||||
Key: &attrKey,
|
||||
Value: &attrValue,
|
||||
}},
|
||||
}
|
||||
|
||||
body, err := json.Marshal(&req)
|
||||
body, err := json.Marshal(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
query := make(url.Values)
|
||||
|
@ -242,7 +246,6 @@ func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnr
|
|||
request, err := http.NewRequest(http.MethodPut, testHost+"/v1/objects?"+query.Encode(), bytes.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
prepareCommonHeaders(request.Header, bearerToken)
|
||||
request.Header.Add("X-Attribute-"+attrKey, attrValue)
|
||||
|
||||
addr := &models.Address{}
|
||||
doRequest(t, httpClient, request, http.StatusOK, addr)
|
||||
|
|
155
gen/models/object_upload.go
Normal file
155
gen/models/object_upload.go
Normal file
|
@ -0,0 +1,155 @@
|
|||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
package models
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/go-openapi/validate"
|
||||
)
|
||||
|
||||
// ObjectUpload object upload
|
||||
// Example: {"attributes":[{"key":"User-Attribute","value":"some-value"}],"containerId":"5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv","fileName":"myFile.txt","payload":"Y29udGVudCBvZiBmaWxl"}
|
||||
//
|
||||
// swagger:model ObjectUpload
|
||||
type ObjectUpload struct {
|
||||
|
||||
// attributes
|
||||
Attributes []*Attribute `json:"attributes"`
|
||||
|
||||
// container Id
|
||||
// Required: true
|
||||
ContainerID *string `json:"containerId"`
|
||||
|
||||
// file name
|
||||
// Required: true
|
||||
FileName *string `json:"fileName"`
|
||||
|
||||
// payload
|
||||
Payload string `json:"payload,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this object upload
|
||||
func (m *ObjectUpload) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateAttributes(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateContainerID(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateFileName(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ObjectUpload) validateAttributes(formats strfmt.Registry) error {
|
||||
if swag.IsZero(m.Attributes) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < len(m.Attributes); i++ {
|
||||
if swag.IsZero(m.Attributes[i]) { // not required
|
||||
continue
|
||||
}
|
||||
|
||||
if m.Attributes[i] != nil {
|
||||
if err := m.Attributes[i].Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("attributes" + "." + strconv.Itoa(i))
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("attributes" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ObjectUpload) validateContainerID(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("containerId", "body", m.ContainerID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ObjectUpload) validateFileName(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("fileName", "body", m.FileName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validate this object upload based on the context it is used
|
||||
func (m *ObjectUpload) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.contextValidateAttributes(ctx, formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ObjectUpload) contextValidateAttributes(ctx context.Context, formats strfmt.Registry) error {
|
||||
|
||||
for i := 0; i < len(m.Attributes); i++ {
|
||||
|
||||
if m.Attributes[i] != nil {
|
||||
if err := m.Attributes[i].ContextValidate(ctx, formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("attributes" + "." + strconv.Itoa(i))
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("attributes" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *ObjectUpload) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *ObjectUpload) UnmarshalBinary(b []byte) error {
|
||||
var res ObjectUpload
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
|
@ -340,27 +340,7 @@ func init() {
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"containerId",
|
||||
"fileName"
|
||||
],
|
||||
"properties": {
|
||||
"containerId": {
|
||||
"type": "string"
|
||||
},
|
||||
"fileName": {
|
||||
"type": "string"
|
||||
},
|
||||
"payload": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"containerId": "5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv",
|
||||
"fileName": "myFile.txt",
|
||||
"payload": "Y29udGVudCBvZiBmaWxl"
|
||||
}
|
||||
"$ref": "#/definitions/ObjectUpload"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -817,6 +797,41 @@ func init() {
|
|||
}
|
||||
}
|
||||
},
|
||||
"ObjectUpload": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"containerId",
|
||||
"fileName"
|
||||
],
|
||||
"properties": {
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Attribute"
|
||||
}
|
||||
},
|
||||
"containerId": {
|
||||
"type": "string"
|
||||
},
|
||||
"fileName": {
|
||||
"type": "string"
|
||||
},
|
||||
"payload": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"attributes": [
|
||||
{
|
||||
"key": "User-Attribute",
|
||||
"value": "some-value"
|
||||
}
|
||||
],
|
||||
"containerId": "5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv",
|
||||
"fileName": "myFile.txt",
|
||||
"payload": "Y29udGVudCBvZiBmaWxl"
|
||||
}
|
||||
},
|
||||
"Operation": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
@ -1424,27 +1439,7 @@ func init() {
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"containerId",
|
||||
"fileName"
|
||||
],
|
||||
"properties": {
|
||||
"containerId": {
|
||||
"type": "string"
|
||||
},
|
||||
"fileName": {
|
||||
"type": "string"
|
||||
},
|
||||
"payload": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"containerId": "5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv",
|
||||
"fileName": "myFile.txt",
|
||||
"payload": "Y29udGVudCBvZiBmaWxl"
|
||||
}
|
||||
"$ref": "#/definitions/ObjectUpload"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -1952,6 +1947,41 @@ func init() {
|
|||
}
|
||||
}
|
||||
},
|
||||
"ObjectUpload": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"containerId",
|
||||
"fileName"
|
||||
],
|
||||
"properties": {
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Attribute"
|
||||
}
|
||||
},
|
||||
"containerId": {
|
||||
"type": "string"
|
||||
},
|
||||
"fileName": {
|
||||
"type": "string"
|
||||
},
|
||||
"payload": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"attributes": [
|
||||
{
|
||||
"key": "User-Attribute",
|
||||
"value": "some-value"
|
||||
}
|
||||
],
|
||||
"containerId": "5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv",
|
||||
"fileName": "myFile.txt",
|
||||
"payload": "Y29udGVudCBvZiBmaWxl"
|
||||
}
|
||||
},
|
||||
"Operation": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
|
@ -6,14 +6,9 @@ package operations
|
|||
// Editing this file might prove futile when you re-run the generate command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/go-openapi/validate"
|
||||
|
||||
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
|
||||
)
|
||||
|
@ -74,80 +69,3 @@ func (o *PutObject) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
o.Context.Respond(rw, r, route.Produces, route, res)
|
||||
|
||||
}
|
||||
|
||||
// PutObjectBody put object body
|
||||
// Example: {"containerId":"5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv","fileName":"myFile.txt","payload":"Y29udGVudCBvZiBmaWxl"}
|
||||
//
|
||||
// swagger:model PutObjectBody
|
||||
type PutObjectBody struct {
|
||||
|
||||
// container Id
|
||||
// Required: true
|
||||
ContainerID *string `json:"containerId"`
|
||||
|
||||
// file name
|
||||
// Required: true
|
||||
FileName *string `json:"fileName"`
|
||||
|
||||
// payload
|
||||
Payload string `json:"payload,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this put object body
|
||||
func (o *PutObjectBody) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := o.validateContainerID(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := o.validateFileName(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *PutObjectBody) validateContainerID(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("object"+"."+"containerId", "body", o.ContainerID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *PutObjectBody) validateFileName(formats strfmt.Registry) error {
|
||||
|
||||
if err := validate.Required("object"+"."+"fileName", "body", o.FileName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this put object body based on context it is used
|
||||
func (o *PutObjectBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (o *PutObjectBody) MarshalBinary() ([]byte, error) {
|
||||
if o == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(o)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (o *PutObjectBody) UnmarshalBinary(b []byte) error {
|
||||
var res PutObjectBody
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*o = res
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ import (
|
|||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/go-openapi/validate"
|
||||
|
||||
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
|
||||
)
|
||||
|
||||
// NewPutObjectParams creates a new PutObjectParams object
|
||||
|
@ -56,7 +58,7 @@ type PutObjectParams struct {
|
|||
Required: true
|
||||
In: body
|
||||
*/
|
||||
Object PutObjectBody
|
||||
Object *models.ObjectUpload
|
||||
/*Use wallect connect signature scheme or not
|
||||
In: query
|
||||
Default: false
|
||||
|
@ -85,7 +87,7 @@ func (o *PutObjectParams) BindRequest(r *http.Request, route *middleware.Matched
|
|||
|
||||
if runtime.HasBody(r) {
|
||||
defer r.Body.Close()
|
||||
var body PutObjectBody
|
||||
var body models.ObjectUpload
|
||||
if err := route.Consumer.Consume(r.Body, &body); err != nil {
|
||||
if err == io.EOF {
|
||||
res = append(res, errors.Required("object", "body", ""))
|
||||
|
@ -104,7 +106,7 @@ func (o *PutObjectParams) BindRequest(r *http.Request, route *middleware.Matched
|
|||
}
|
||||
|
||||
if len(res) == 0 {
|
||||
o.Object = body
|
||||
o.Object = &body
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -5,6 +5,9 @@ import (
|
|||
"crypto/ecdsa"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
|
@ -19,8 +22,6 @@ import (
|
|||
"github.com/nspcc-dev/neofs-sdk-go/pool"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/token"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PutObjects handler that uploads object to NeoFS.
|
||||
|
@ -48,7 +49,7 @@ func (a *API) PutObjects(params operations.PutObjectParams, principal *models.Pr
|
|||
DefaultTimestamp: a.defaultTimestamp,
|
||||
DefaultFileName: *params.Object.FileName,
|
||||
}
|
||||
attributes, err := GetObjectAttributes(ctx, params.HTTPRequest.Header, a.pool, prm)
|
||||
attributes, err := GetObjectAttributes(ctx, a.pool, params.Object.Attributes, prm)
|
||||
if err != nil {
|
||||
return errorResponse.WithPayload(models.Error(err.Error()))
|
||||
}
|
||||
|
|
|
@ -87,35 +87,38 @@ func filterHeaders(header http.Header) map[string]string {
|
|||
}
|
||||
|
||||
// GetObjectAttributes forms object attributes from request headers.
|
||||
func GetObjectAttributes(ctx context.Context, header http.Header, pool *pool.Pool, prm PrmAttributes) ([]object.Attribute, error) {
|
||||
filtered := filterHeaders(header)
|
||||
if needParseExpiration(filtered) {
|
||||
func GetObjectAttributes(ctx context.Context, pool *pool.Pool, attrs []*models.Attribute, prm PrmAttributes) ([]object.Attribute, error) {
|
||||
headers := make(map[string]string, len(attrs))
|
||||
|
||||
for _, attr := range attrs {
|
||||
headers[*attr.Key] = *attr.Value
|
||||
}
|
||||
delete(headers, object.AttributeFileName)
|
||||
|
||||
if needParseExpiration(headers) {
|
||||
epochDuration, err := getEpochDurations(ctx, pool)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get epoch durations from network info: %w", err)
|
||||
}
|
||||
if err = prepareExpirationHeader(filtered, epochDuration); err != nil {
|
||||
if err = prepareExpirationHeader(headers, epochDuration); err != nil {
|
||||
return nil, fmt.Errorf("could not prepare expiration header: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
attributes := make([]object.Attribute, 0, len(filtered))
|
||||
// prepares attributes from filtered headers
|
||||
for key, val := range filtered {
|
||||
attributes := make([]object.Attribute, 0, len(headers))
|
||||
for key, val := range headers {
|
||||
attribute := object.NewAttribute()
|
||||
attribute.SetKey(key)
|
||||
attribute.SetValue(val)
|
||||
attributes = append(attributes, *attribute)
|
||||
}
|
||||
// sets FileName attribute if it wasn't set from header
|
||||
if _, ok := filtered[object.AttributeFileName]; !ok && prm.DefaultFileName != "" {
|
||||
filename := object.NewAttribute()
|
||||
filename.SetKey(object.AttributeFileName)
|
||||
filename.SetValue(prm.DefaultFileName)
|
||||
attributes = append(attributes, *filename)
|
||||
}
|
||||
// sets Timestamp attribute if it wasn't set from header and enabled by settings
|
||||
if _, ok := filtered[object.AttributeTimestamp]; !ok && prm.DefaultTimestamp {
|
||||
|
||||
filename := object.NewAttribute()
|
||||
filename.SetKey(object.AttributeFileName)
|
||||
filename.SetValue(prm.DefaultFileName)
|
||||
attributes = append(attributes, *filename)
|
||||
|
||||
if _, ok := headers[object.AttributeTimestamp]; !ok && prm.DefaultTimestamp {
|
||||
timestamp := object.NewAttribute()
|
||||
timestamp.SetKey(object.AttributeTimestamp)
|
||||
timestamp.SetValue(strconv.FormatInt(time.Now().Unix(), 10))
|
||||
|
|
|
@ -107,21 +107,7 @@ paths:
|
|||
name: object
|
||||
description: Object info to upload
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
containerId:
|
||||
type: string
|
||||
fileName:
|
||||
type: string
|
||||
payload:
|
||||
type: string
|
||||
required:
|
||||
- containerId
|
||||
- fileName
|
||||
example:
|
||||
containerId: 5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv
|
||||
fileName: myFile.txt
|
||||
payload: Y29udGVudCBvZiBmaWxl
|
||||
$ref: '#/definitions/ObjectUpload'
|
||||
consumes:
|
||||
- application/json
|
||||
produces:
|
||||
|
@ -618,6 +604,29 @@ definitions:
|
|||
type: string
|
||||
required:
|
||||
- address
|
||||
ObjectUpload:
|
||||
type: object
|
||||
properties:
|
||||
containerId:
|
||||
type: string
|
||||
fileName:
|
||||
type: string
|
||||
payload:
|
||||
type: string
|
||||
attributes:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Attribute'
|
||||
required:
|
||||
- containerId
|
||||
- fileName
|
||||
example:
|
||||
containerId: 5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv
|
||||
fileName: myFile.txt
|
||||
payload: Y29udGVudCBvZiBmaWxl
|
||||
attributes:
|
||||
- key: User-Attribute
|
||||
value: some-value
|
||||
ObjectInfo:
|
||||
type: object
|
||||
properties:
|
||||
|
|
Loading…
Reference in a new issue