[#1] Use body to provide object attribute

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2022-04-20 17:52:41 +03:00 committed by Alex Vanin
parent 3727f5561d
commit 06060348ae
8 changed files with 285 additions and 164 deletions

View file

@ -227,13 +227,17 @@ func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnr
attrKey: attrValue, attrKey: attrValue,
} }
req := operations.PutObjectBody{ req := &models.ObjectUpload{
ContainerID: handlers.NewString(cnrID.String()), ContainerID: handlers.NewString(cnrID.String()),
FileName: handlers.NewString("newFile.txt"), FileName: handlers.NewString("newFile.txt"),
Payload: base64.StdEncoding.EncodeToString([]byte(content)), 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) require.NoError(t, err)
query := make(url.Values) 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)) request, err := http.NewRequest(http.MethodPut, testHost+"/v1/objects?"+query.Encode(), bytes.NewReader(body))
require.NoError(t, err) require.NoError(t, err)
prepareCommonHeaders(request.Header, bearerToken) prepareCommonHeaders(request.Header, bearerToken)
request.Header.Add("X-Attribute-"+attrKey, attrValue)
addr := &models.Address{} addr := &models.Address{}
doRequest(t, httpClient, request, http.StatusOK, addr) doRequest(t, httpClient, request, http.StatusOK, addr)

155
gen/models/object_upload.go Normal file
View 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
}

View file

@ -340,27 +340,7 @@ func init() {
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"type": "object", "$ref": "#/definitions/ObjectUpload"
"required": [
"containerId",
"fileName"
],
"properties": {
"containerId": {
"type": "string"
},
"fileName": {
"type": "string"
},
"payload": {
"type": "string"
}
},
"example": {
"containerId": "5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv",
"fileName": "myFile.txt",
"payload": "Y29udGVudCBvZiBmaWxl"
}
} }
} }
], ],
@ -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": { "Operation": {
"type": "string", "type": "string",
"enum": [ "enum": [
@ -1424,27 +1439,7 @@ func init() {
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"type": "object", "$ref": "#/definitions/ObjectUpload"
"required": [
"containerId",
"fileName"
],
"properties": {
"containerId": {
"type": "string"
},
"fileName": {
"type": "string"
},
"payload": {
"type": "string"
}
},
"example": {
"containerId": "5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv",
"fileName": "myFile.txt",
"payload": "Y29udGVudCBvZiBmaWxl"
}
} }
} }
], ],
@ -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": { "Operation": {
"type": "string", "type": "string",
"enum": [ "enum": [

View file

@ -6,14 +6,9 @@ package operations
// Editing this file might prove futile when you re-run the generate command // Editing this file might prove futile when you re-run the generate command
import ( import (
"context"
"net/http" "net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware" "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" "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) 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
}

View file

@ -16,6 +16,8 @@ import (
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
"github.com/go-openapi/swag" "github.com/go-openapi/swag"
"github.com/go-openapi/validate" "github.com/go-openapi/validate"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
) )
// NewPutObjectParams creates a new PutObjectParams object // NewPutObjectParams creates a new PutObjectParams object
@ -56,7 +58,7 @@ type PutObjectParams struct {
Required: true Required: true
In: body In: body
*/ */
Object PutObjectBody Object *models.ObjectUpload
/*Use wallect connect signature scheme or not /*Use wallect connect signature scheme or not
In: query In: query
Default: false Default: false
@ -85,7 +87,7 @@ func (o *PutObjectParams) BindRequest(r *http.Request, route *middleware.Matched
if runtime.HasBody(r) { if runtime.HasBody(r) {
defer r.Body.Close() defer r.Body.Close()
var body PutObjectBody var body models.ObjectUpload
if err := route.Consumer.Consume(r.Body, &body); err != nil { if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF { if err == io.EOF {
res = append(res, errors.Required("object", "body", "")) 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 { if len(res) == 0 {
o.Object = body o.Object = &body
} }
} }
} else { } else {

View file

@ -5,6 +5,9 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io"
"strings"
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neofs-api-go/v2/acl" "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/pool"
"github.com/nspcc-dev/neofs-sdk-go/token" "github.com/nspcc-dev/neofs-sdk-go/token"
"go.uber.org/zap" "go.uber.org/zap"
"io"
"strings"
) )
// PutObjects handler that uploads object to NeoFS. // PutObjects handler that uploads object to NeoFS.
@ -48,7 +49,7 @@ func (a *API) PutObjects(params operations.PutObjectParams, principal *models.Pr
DefaultTimestamp: a.defaultTimestamp, DefaultTimestamp: a.defaultTimestamp,
DefaultFileName: *params.Object.FileName, 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 { if err != nil {
return errorResponse.WithPayload(models.Error(err.Error())) return errorResponse.WithPayload(models.Error(err.Error()))
} }

View file

@ -87,35 +87,38 @@ func filterHeaders(header http.Header) map[string]string {
} }
// GetObjectAttributes forms object attributes from request headers. // GetObjectAttributes forms object attributes from request headers.
func GetObjectAttributes(ctx context.Context, header http.Header, pool *pool.Pool, prm PrmAttributes) ([]object.Attribute, error) { func GetObjectAttributes(ctx context.Context, pool *pool.Pool, attrs []*models.Attribute, prm PrmAttributes) ([]object.Attribute, error) {
filtered := filterHeaders(header) headers := make(map[string]string, len(attrs))
if needParseExpiration(filtered) {
for _, attr := range attrs {
headers[*attr.Key] = *attr.Value
}
delete(headers, object.AttributeFileName)
if needParseExpiration(headers) {
epochDuration, err := getEpochDurations(ctx, pool) epochDuration, err := getEpochDurations(ctx, pool)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get epoch durations from network info: %w", err) 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) return nil, fmt.Errorf("could not prepare expiration header: %w", err)
} }
} }
attributes := make([]object.Attribute, 0, len(filtered)) attributes := make([]object.Attribute, 0, len(headers))
// prepares attributes from filtered headers for key, val := range headers {
for key, val := range filtered {
attribute := object.NewAttribute() attribute := object.NewAttribute()
attribute.SetKey(key) attribute.SetKey(key)
attribute.SetValue(val) attribute.SetValue(val)
attributes = append(attributes, *attribute) 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 := object.NewAttribute()
filename.SetKey(object.AttributeFileName) filename.SetKey(object.AttributeFileName)
filename.SetValue(prm.DefaultFileName) filename.SetValue(prm.DefaultFileName)
attributes = append(attributes, *filename) attributes = append(attributes, *filename)
}
// sets Timestamp attribute if it wasn't set from header and enabled by settings if _, ok := headers[object.AttributeTimestamp]; !ok && prm.DefaultTimestamp {
if _, ok := filtered[object.AttributeTimestamp]; !ok && prm.DefaultTimestamp {
timestamp := object.NewAttribute() timestamp := object.NewAttribute()
timestamp.SetKey(object.AttributeTimestamp) timestamp.SetKey(object.AttributeTimestamp)
timestamp.SetValue(strconv.FormatInt(time.Now().Unix(), 10)) timestamp.SetValue(strconv.FormatInt(time.Now().Unix(), 10))

View file

@ -107,21 +107,7 @@ paths:
name: object name: object
description: Object info to upload description: Object info to upload
schema: schema:
type: object $ref: '#/definitions/ObjectUpload'
properties:
containerId:
type: string
fileName:
type: string
payload:
type: string
required:
- containerId
- fileName
example:
containerId: 5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv
fileName: myFile.txt
payload: Y29udGVudCBvZiBmaWxl
consumes: consumes:
- application/json - application/json
produces: produces:
@ -618,6 +604,29 @@ definitions:
type: string type: string
required: required:
- address - 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: ObjectInfo:
type: object type: object
properties: properties: