github.com/vnpaycloud-console/gophercloud/v2@v2.0.5/openstack/objectstorage/v1/objects/results.go (about)

     1  package objects
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"net/url"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/vnpaycloud-console/gophercloud/v2"
    12  	"github.com/vnpaycloud-console/gophercloud/v2/pagination"
    13  )
    14  
    15  // Object is a structure that holds information related to a storage object.
    16  type Object struct {
    17  	// Bytes is the total number of bytes that comprise the object.
    18  	Bytes int64 `json:"bytes"`
    19  
    20  	// ContentType is the content type of the object.
    21  	ContentType string `json:"content_type"`
    22  
    23  	// Hash represents the MD5 checksum value of the object's content.
    24  	Hash string `json:"hash"`
    25  
    26  	// LastModified is the time the object was last modified.
    27  	LastModified time.Time `json:"-"`
    28  
    29  	// Name is the unique name for the object.
    30  	Name string `json:"name"`
    31  
    32  	// Subdir denotes if the result contains a subdir.
    33  	Subdir string `json:"subdir"`
    34  
    35  	// IsLatest indicates whether the object version is the latest one.
    36  	IsLatest bool `json:"is_latest"`
    37  
    38  	// VersionID contains a version ID of the object, when container
    39  	// versioning is enabled.
    40  	VersionID string `json:"version_id"`
    41  }
    42  
    43  func (r *Object) UnmarshalJSON(b []byte) error {
    44  	type tmp Object
    45  	var s *struct {
    46  		tmp
    47  		LastModified string `json:"last_modified"`
    48  	}
    49  
    50  	err := json.Unmarshal(b, &s)
    51  	if err != nil {
    52  		return err
    53  	}
    54  
    55  	*r = Object(s.tmp)
    56  
    57  	if s.LastModified != "" {
    58  		t, err := time.Parse(gophercloud.RFC3339MilliNoZ, s.LastModified)
    59  		if err != nil {
    60  			t, err = time.Parse(gophercloud.RFC3339Milli, s.LastModified)
    61  			if err != nil {
    62  				return err
    63  			}
    64  		}
    65  		r.LastModified = t
    66  	}
    67  
    68  	return nil
    69  }
    70  
    71  // ObjectPage is a single page of objects that is returned from a call to the
    72  // List function.
    73  type ObjectPage struct {
    74  	pagination.MarkerPageBase
    75  }
    76  
    77  // IsEmpty returns true if a ListResult contains no object names.
    78  func (r ObjectPage) IsEmpty() (bool, error) {
    79  	if r.StatusCode == 204 {
    80  		return true, nil
    81  	}
    82  
    83  	names, err := ExtractNames(r)
    84  	return len(names) == 0, err
    85  }
    86  
    87  // LastMarker returns the last object name in a ListResult.
    88  func (r ObjectPage) LastMarker() (string, error) {
    89  	return extractLastMarker(r)
    90  }
    91  
    92  // ExtractInfo is a function that takes a page of objects and returns their
    93  // full information.
    94  func ExtractInfo(r pagination.Page) ([]Object, error) {
    95  	var s []Object
    96  	err := (r.(ObjectPage)).ExtractInto(&s)
    97  	return s, err
    98  }
    99  
   100  // ExtractNames is a function that takes a page of objects and returns only
   101  // their names.
   102  func ExtractNames(r pagination.Page) ([]string, error) {
   103  	casted := r.(ObjectPage)
   104  	ct := casted.Header.Get("Content-Type")
   105  	switch {
   106  	case strings.HasPrefix(ct, "application/json"):
   107  		parsed, err := ExtractInfo(r)
   108  		if err != nil {
   109  			return nil, err
   110  		}
   111  
   112  		names := make([]string, 0, len(parsed))
   113  		for _, object := range parsed {
   114  			if object.Subdir != "" {
   115  				names = append(names, object.Subdir)
   116  			} else {
   117  				names = append(names, object.Name)
   118  			}
   119  		}
   120  
   121  		return names, nil
   122  	case strings.HasPrefix(ct, "text/plain"):
   123  		names := make([]string, 0, 50)
   124  
   125  		body := string(r.(ObjectPage).Body.([]uint8))
   126  		for _, name := range strings.Split(body, "\n") {
   127  			if len(name) > 0 {
   128  				names = append(names, name)
   129  			}
   130  		}
   131  
   132  		return names, nil
   133  	case strings.HasPrefix(ct, "text/html"):
   134  		return []string{}, nil
   135  	default:
   136  		return nil, fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct)
   137  	}
   138  }
   139  
   140  // DownloadHeader represents the headers returned in the response from a
   141  // Download request.
   142  type DownloadHeader struct {
   143  	AcceptRanges       string    `json:"Accept-Ranges"`
   144  	ContentDisposition string    `json:"Content-Disposition"`
   145  	ContentEncoding    string    `json:"Content-Encoding"`
   146  	ContentLength      int64     `json:"Content-Length,string"`
   147  	ContentType        string    `json:"Content-Type"`
   148  	Date               time.Time `json:"-"`
   149  	DeleteAt           time.Time `json:"-"`
   150  	ETag               string    `json:"Etag"`
   151  	LastModified       time.Time `json:"-"`
   152  	ObjectManifest     string    `json:"X-Object-Manifest"`
   153  	StaticLargeObject  bool      `json:"-"`
   154  	TransID            string    `json:"X-Trans-Id"`
   155  	ObjectVersionID    string    `json:"X-Object-Version-Id"`
   156  }
   157  
   158  func (r *DownloadHeader) UnmarshalJSON(b []byte) error {
   159  	type tmp DownloadHeader
   160  	var s struct {
   161  		tmp
   162  		Date              gophercloud.JSONRFC1123 `json:"Date"`
   163  		DeleteAt          gophercloud.JSONUnix    `json:"X-Delete-At"`
   164  		LastModified      gophercloud.JSONRFC1123 `json:"Last-Modified"`
   165  		StaticLargeObject any                     `json:"X-Static-Large-Object"`
   166  	}
   167  	err := json.Unmarshal(b, &s)
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	*r = DownloadHeader(s.tmp)
   173  
   174  	switch t := s.StaticLargeObject.(type) {
   175  	case string:
   176  		if t == "True" || t == "true" {
   177  			r.StaticLargeObject = true
   178  		}
   179  	case bool:
   180  		r.StaticLargeObject = t
   181  	}
   182  
   183  	r.Date = time.Time(s.Date)
   184  	r.DeleteAt = time.Time(s.DeleteAt)
   185  	r.LastModified = time.Time(s.LastModified)
   186  
   187  	return nil
   188  }
   189  
   190  // DownloadResult is a *http.Response that is returned from a call to the
   191  // Download function.
   192  type DownloadResult struct {
   193  	gophercloud.HeaderResult
   194  	Body io.ReadCloser
   195  }
   196  
   197  // Extract will return a struct of headers returned from a call to Download.
   198  func (r DownloadResult) Extract() (*DownloadHeader, error) {
   199  	var s DownloadHeader
   200  	err := r.ExtractInto(&s)
   201  	return &s, err
   202  }
   203  
   204  // ExtractContent is a function that takes a DownloadResult's io.Reader body
   205  // and reads all available data into a slice of bytes. Please be aware that due
   206  // the nature of io.Reader is forward-only - meaning that it can only be read
   207  // once and not rewound. You can recreate a reader from the output of this
   208  // function by using bytes.NewReader(downloadBytes)
   209  func (r *DownloadResult) ExtractContent() ([]byte, error) {
   210  	if r.Err != nil {
   211  		return nil, r.Err
   212  	}
   213  	defer r.Body.Close()
   214  	body, err := io.ReadAll(r.Body)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	return body, nil
   219  }
   220  
   221  // GetHeader represents the headers returned in the response from a Get request.
   222  type GetHeader struct {
   223  	ContentDisposition string    `json:"Content-Disposition"`
   224  	ContentEncoding    string    `json:"Content-Encoding"`
   225  	ContentLength      int64     `json:"Content-Length,string"`
   226  	ContentType        string    `json:"Content-Type"`
   227  	Date               time.Time `json:"-"`
   228  	DeleteAt           time.Time `json:"-"`
   229  	ETag               string    `json:"Etag"`
   230  	LastModified       time.Time `json:"-"`
   231  	ObjectManifest     string    `json:"X-Object-Manifest"`
   232  	StaticLargeObject  bool      `json:"-"`
   233  	TransID            string    `json:"X-Trans-Id"`
   234  	ObjectVersionID    string    `json:"X-Object-Version-Id"`
   235  }
   236  
   237  func (r *GetHeader) UnmarshalJSON(b []byte) error {
   238  	type tmp GetHeader
   239  	var s struct {
   240  		tmp
   241  		Date              gophercloud.JSONRFC1123 `json:"Date"`
   242  		DeleteAt          gophercloud.JSONUnix    `json:"X-Delete-At"`
   243  		LastModified      gophercloud.JSONRFC1123 `json:"Last-Modified"`
   244  		StaticLargeObject any                     `json:"X-Static-Large-Object"`
   245  	}
   246  	err := json.Unmarshal(b, &s)
   247  	if err != nil {
   248  		return err
   249  	}
   250  
   251  	*r = GetHeader(s.tmp)
   252  
   253  	switch t := s.StaticLargeObject.(type) {
   254  	case string:
   255  		if t == "True" || t == "true" {
   256  			r.StaticLargeObject = true
   257  		}
   258  	case bool:
   259  		r.StaticLargeObject = t
   260  	}
   261  
   262  	r.Date = time.Time(s.Date)
   263  	r.DeleteAt = time.Time(s.DeleteAt)
   264  	r.LastModified = time.Time(s.LastModified)
   265  
   266  	return nil
   267  }
   268  
   269  // GetResult is a *http.Response that is returned from a call to the Get
   270  // function.
   271  type GetResult struct {
   272  	gophercloud.HeaderResult
   273  }
   274  
   275  // Extract will return a struct of headers returned from a call to Get.
   276  func (r GetResult) Extract() (*GetHeader, error) {
   277  	var s GetHeader
   278  	err := r.ExtractInto(&s)
   279  	return &s, err
   280  }
   281  
   282  // ExtractMetadata is a function that takes a GetResult (of type *http.Response)
   283  // and returns the custom metadata associated with the object.
   284  func (r GetResult) ExtractMetadata() (map[string]string, error) {
   285  	if r.Err != nil {
   286  		return nil, r.Err
   287  	}
   288  	metadata := make(map[string]string)
   289  	for k, v := range r.Header {
   290  		if strings.HasPrefix(k, "X-Object-Meta-") {
   291  			key := strings.TrimPrefix(k, "X-Object-Meta-")
   292  			metadata[key] = v[0]
   293  		}
   294  	}
   295  	return metadata, nil
   296  }
   297  
   298  // CreateHeader represents the headers returned in the response from a
   299  // Create request.
   300  type CreateHeader struct {
   301  	ContentLength   int64     `json:"Content-Length,string"`
   302  	ContentType     string    `json:"Content-Type"`
   303  	Date            time.Time `json:"-"`
   304  	ETag            string    `json:"Etag"`
   305  	LastModified    time.Time `json:"-"`
   306  	TransID         string    `json:"X-Trans-Id"`
   307  	ObjectVersionID string    `json:"X-Object-Version-Id"`
   308  }
   309  
   310  func (r *CreateHeader) UnmarshalJSON(b []byte) error {
   311  	type tmp CreateHeader
   312  	var s struct {
   313  		tmp
   314  		Date         gophercloud.JSONRFC1123 `json:"Date"`
   315  		LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
   316  	}
   317  	err := json.Unmarshal(b, &s)
   318  	if err != nil {
   319  		return err
   320  	}
   321  
   322  	*r = CreateHeader(s.tmp)
   323  
   324  	r.Date = time.Time(s.Date)
   325  	r.LastModified = time.Time(s.LastModified)
   326  
   327  	return nil
   328  }
   329  
   330  // CreateResult represents the result of a create operation.
   331  type CreateResult struct {
   332  	gophercloud.HeaderResult
   333  }
   334  
   335  // Extract will return a struct of headers returned from a call to Create.
   336  func (r CreateResult) Extract() (*CreateHeader, error) {
   337  	var s CreateHeader
   338  	err := r.ExtractInto(&s)
   339  	return &s, err
   340  }
   341  
   342  // UpdateHeader represents the headers returned in the response from a
   343  // Update request.
   344  type UpdateHeader struct {
   345  	ContentLength   int64     `json:"Content-Length,string"`
   346  	ContentType     string    `json:"Content-Type"`
   347  	Date            time.Time `json:"-"`
   348  	TransID         string    `json:"X-Trans-Id"`
   349  	ObjectVersionID string    `json:"X-Object-Version-Id"`
   350  }
   351  
   352  func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
   353  	type tmp UpdateHeader
   354  	var s struct {
   355  		tmp
   356  		Date gophercloud.JSONRFC1123 `json:"Date"`
   357  	}
   358  	err := json.Unmarshal(b, &s)
   359  	if err != nil {
   360  		return err
   361  	}
   362  
   363  	*r = UpdateHeader(s.tmp)
   364  
   365  	r.Date = time.Time(s.Date)
   366  
   367  	return nil
   368  }
   369  
   370  // UpdateResult represents the result of an update operation.
   371  type UpdateResult struct {
   372  	gophercloud.HeaderResult
   373  }
   374  
   375  // Extract will return a struct of headers returned from a call to Update.
   376  func (r UpdateResult) Extract() (*UpdateHeader, error) {
   377  	var s UpdateHeader
   378  	err := r.ExtractInto(&s)
   379  	return &s, err
   380  }
   381  
   382  // DeleteHeader represents the headers returned in the response from a
   383  // Delete request.
   384  type DeleteHeader struct {
   385  	ContentLength          int64     `json:"Content-Length,string"`
   386  	ContentType            string    `json:"Content-Type"`
   387  	Date                   time.Time `json:"-"`
   388  	TransID                string    `json:"X-Trans-Id"`
   389  	ObjectVersionID        string    `json:"X-Object-Version-Id"`
   390  	ObjectCurrentVersionID string    `json:"X-Object-Current-Version-Id"`
   391  }
   392  
   393  func (r *DeleteHeader) UnmarshalJSON(b []byte) error {
   394  	type tmp DeleteHeader
   395  	var s struct {
   396  		tmp
   397  		Date gophercloud.JSONRFC1123 `json:"Date"`
   398  	}
   399  	err := json.Unmarshal(b, &s)
   400  	if err != nil {
   401  		return err
   402  	}
   403  
   404  	*r = DeleteHeader(s.tmp)
   405  
   406  	r.Date = time.Time(s.Date)
   407  
   408  	return nil
   409  }
   410  
   411  // DeleteResult represents the result of a delete operation.
   412  type DeleteResult struct {
   413  	gophercloud.HeaderResult
   414  }
   415  
   416  // Extract will return a struct of headers returned from a call to Delete.
   417  func (r DeleteResult) Extract() (*DeleteHeader, error) {
   418  	var s DeleteHeader
   419  	err := r.ExtractInto(&s)
   420  	return &s, err
   421  }
   422  
   423  // CopyHeader represents the headers returned in the response from a
   424  // Copy request.
   425  type CopyHeader struct {
   426  	ContentLength          int64     `json:"Content-Length,string"`
   427  	ContentType            string    `json:"Content-Type"`
   428  	CopiedFrom             string    `json:"X-Copied-From"`
   429  	CopiedFromLastModified time.Time `json:"-"`
   430  	Date                   time.Time `json:"-"`
   431  	ETag                   string    `json:"Etag"`
   432  	LastModified           time.Time `json:"-"`
   433  	TransID                string    `json:"X-Trans-Id"`
   434  	ObjectVersionID        string    `json:"X-Object-Version-Id"`
   435  }
   436  
   437  func (r *CopyHeader) UnmarshalJSON(b []byte) error {
   438  	type tmp CopyHeader
   439  	var s struct {
   440  		tmp
   441  		CopiedFromLastModified gophercloud.JSONRFC1123 `json:"X-Copied-From-Last-Modified"`
   442  		Date                   gophercloud.JSONRFC1123 `json:"Date"`
   443  		LastModified           gophercloud.JSONRFC1123 `json:"Last-Modified"`
   444  	}
   445  	err := json.Unmarshal(b, &s)
   446  	if err != nil {
   447  		return err
   448  	}
   449  
   450  	*r = CopyHeader(s.tmp)
   451  
   452  	r.Date = time.Time(s.Date)
   453  	r.CopiedFromLastModified = time.Time(s.CopiedFromLastModified)
   454  	r.LastModified = time.Time(s.LastModified)
   455  
   456  	return nil
   457  }
   458  
   459  // CopyResult represents the result of a copy operation.
   460  type CopyResult struct {
   461  	gophercloud.HeaderResult
   462  }
   463  
   464  // Extract will return a struct of headers returned from a call to Copy.
   465  func (r CopyResult) Extract() (*CopyHeader, error) {
   466  	var s CopyHeader
   467  	err := r.ExtractInto(&s)
   468  	return &s, err
   469  }
   470  
   471  type BulkDeleteResponse struct {
   472  	ResponseStatus string     `json:"Response Status"`
   473  	ResponseBody   string     `json:"Response Body"`
   474  	Errors         [][]string `json:"Errors"`
   475  	NumberDeleted  int        `json:"Number Deleted"`
   476  	NumberNotFound int        `json:"Number Not Found"`
   477  }
   478  
   479  // BulkDeleteResult represents the result of a bulk delete operation. To extract
   480  // the response object from the HTTP response, call its Extract method.
   481  type BulkDeleteResult struct {
   482  	gophercloud.Result
   483  }
   484  
   485  // Extract will return a BulkDeleteResponse struct returned from a BulkDelete
   486  // call.
   487  func (r BulkDeleteResult) Extract() (*BulkDeleteResponse, error) {
   488  	var s BulkDeleteResponse
   489  	err := r.ExtractInto(&s)
   490  	return &s, err
   491  }
   492  
   493  // extractLastMarker is a function that takes a page of objects and returns the
   494  // marker for the page. This can either be a subdir or the last object's name.
   495  func extractLastMarker(r pagination.Page) (string, error) {
   496  	casted := r.(ObjectPage)
   497  
   498  	// If a delimiter was requested, check if a subdir exists.
   499  	queryParams, err := url.ParseQuery(casted.URL.RawQuery)
   500  	if err != nil {
   501  		return "", err
   502  	}
   503  
   504  	var delimeter bool
   505  	if v, ok := queryParams["delimiter"]; ok && len(v) > 0 {
   506  		delimeter = true
   507  	}
   508  
   509  	ct := casted.Header.Get("Content-Type")
   510  	switch {
   511  	case strings.HasPrefix(ct, "application/json"):
   512  		parsed, err := ExtractInfo(r)
   513  		if err != nil {
   514  			return "", err
   515  		}
   516  
   517  		var lastObject Object
   518  		if len(parsed) > 0 {
   519  			lastObject = parsed[len(parsed)-1]
   520  		}
   521  
   522  		if !delimeter {
   523  			return lastObject.Name, nil
   524  		}
   525  
   526  		if lastObject.Name != "" {
   527  			return lastObject.Name, nil
   528  		}
   529  
   530  		return lastObject.Subdir, nil
   531  	case strings.HasPrefix(ct, "text/plain"):
   532  		names := make([]string, 0, 50)
   533  
   534  		body := string(r.(ObjectPage).Body.([]uint8))
   535  		for _, name := range strings.Split(body, "\n") {
   536  			if len(name) > 0 {
   537  				names = append(names, name)
   538  			}
   539  		}
   540  
   541  		return names[len(names)-1], err
   542  	case strings.HasPrefix(ct, "text/html"):
   543  		return "", nil
   544  	default:
   545  		return "", fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct)
   546  	}
   547  }