...

Source file src/github.com/go-kivik/kivik/v4/attachments.go

Documentation: github.com/go-kivik/kivik/v4

     1  // Licensed under the Apache License, Version 2.0 (the "License"); you may not
     2  // use this file except in compliance with the License. You may obtain a copy of
     3  // the License at
     4  //
     5  //  http://www.apache.org/licenses/LICENSE-2.0
     6  //
     7  // Unless required by applicable law or agreed to in writing, software
     8  // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     9  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    10  // License for the specific language governing permissions and limitations under
    11  // the License.
    12  
    13  package kivik
    14  
    15  import (
    16  	"bytes"
    17  	"encoding/json"
    18  	"io"
    19  
    20  	"github.com/go-kivik/kivik/v4/driver"
    21  )
    22  
    23  // Attachments is a collection of one or more file attachments.
    24  type Attachments map[string]*Attachment
    25  
    26  // Get fetches the requested attachment, or returns nil if it does not exist.
    27  func (a *Attachments) Get(filename string) *Attachment {
    28  	return map[string]*Attachment(*a)[filename]
    29  }
    30  
    31  // Set sets the attachment associated with filename in the collection,
    32  // replacing it if it already exists.
    33  func (a *Attachments) Set(filename string, att *Attachment) {
    34  	map[string]*Attachment(*a)[filename] = att
    35  }
    36  
    37  // Delete removes the specified file from the collection.
    38  func (a *Attachments) Delete(filename string) {
    39  	delete(map[string]*Attachment(*a), filename)
    40  }
    41  
    42  // Attachment represents a file attachment on a CouchDB document.
    43  type Attachment struct {
    44  	// Filename is the name of the attachment.
    45  	Filename string `json:"-"`
    46  
    47  	// ContentType is the Content-Type type of the attachment.
    48  	ContentType string `json:"content_type"`
    49  
    50  	// Stub will be true if the data structure only represents file metadata,
    51  	// and contains no actual content. Stub will be true when returned by
    52  	// [DB.GetAttachmentMeta], or when included in a document without the
    53  	// 'include_docs' option.
    54  	Stub bool `json:"stub"`
    55  
    56  	// Follows will be true when reading attachments in multipart/related
    57  	// format.
    58  	Follows bool `json:"follows"`
    59  
    60  	// Content represents the attachment's content.
    61  	//
    62  	// Kivik will always return a non-nil Content, even for 0-byte attachments
    63  	// or when Stub is true. It is the caller's responsibility to close
    64  	// Content.
    65  	Content io.ReadCloser `json:"-"`
    66  
    67  	// Size records the uncompressed size of the attachment. The value -1
    68  	// indicates that the length is unknown. Unless [Attachment.Stub] is true,
    69  	// values >= 0 indicate that the given number of bytes may be read from
    70  	// [Attachment.Content].
    71  	Size int64 `json:"length"`
    72  
    73  	// Used compression codec, if any. Will be the empty string if the
    74  	// attachment is uncompressed.
    75  	ContentEncoding string `json:"encoding"`
    76  
    77  	// EncodedLength records the compressed attachment size in bytes. Only
    78  	// meaningful when [Attachment.ContentEncoding] is defined.
    79  	EncodedLength int64 `json:"encoded_length"`
    80  
    81  	// RevPos is the revision number when attachment was added.
    82  	RevPos int64 `json:"revpos"`
    83  
    84  	// Digest is the content hash digest.
    85  	Digest string `json:"digest"`
    86  }
    87  
    88  // bufCloser wraps a *bytes.Buffer to create an io.ReadCloser
    89  type bufCloser struct {
    90  	*bytes.Buffer
    91  }
    92  
    93  var _ io.ReadCloser = &bufCloser{}
    94  
    95  func (b *bufCloser) Close() error { return nil }
    96  
    97  // validate returns an error if the attachment is invalid.
    98  func (a *Attachment) validate() error {
    99  	if a == nil {
   100  		return missingArg("attachment")
   101  	}
   102  	if a.Filename == "" {
   103  		return missingArg("filename")
   104  	}
   105  	return nil
   106  }
   107  
   108  // MarshalJSON satisfies the [encoding/json.Marshaler] interface.
   109  func (a *Attachment) MarshalJSON() ([]byte, error) {
   110  	type jsonAttachment struct {
   111  		ContentType string `json:"content_type"`
   112  		Stub        *bool  `json:"stub,omitempty"`
   113  		Follows     *bool  `json:"follows,omitempty"`
   114  		Size        int64  `json:"length,omitempty"`
   115  		RevPos      int64  `json:"revpos,omitempty"`
   116  		Data        []byte `json:"data,omitempty"`
   117  		Digest      string `json:"digest,omitempty"`
   118  	}
   119  	att := &jsonAttachment{
   120  		ContentType: a.ContentType,
   121  		Size:        a.Size,
   122  		RevPos:      a.RevPos,
   123  		Digest:      a.Digest,
   124  	}
   125  	switch {
   126  	case a.Stub:
   127  		att.Stub = &a.Stub
   128  	case a.Follows:
   129  		att.Follows = &a.Follows
   130  	default:
   131  		defer a.Content.Close() // nolint: errcheck
   132  		data, err := io.ReadAll(a.Content)
   133  		if err != nil {
   134  			return nil, err
   135  		}
   136  		att.Data = data
   137  	}
   138  	return json.Marshal(att)
   139  }
   140  
   141  // UnmarshalJSON implements the [encoding/json.Unmarshaler] interface.
   142  func (a *Attachment) UnmarshalJSON(data []byte) error {
   143  	type clone Attachment
   144  	type jsonAtt struct {
   145  		clone
   146  		Data []byte `json:"data"`
   147  	}
   148  	var att jsonAtt
   149  	if err := json.Unmarshal(data, &att); err != nil {
   150  		return err
   151  	}
   152  	*a = Attachment(att.clone)
   153  	if att.Data != nil {
   154  		a.Content = io.NopCloser(bytes.NewReader(att.Data))
   155  	} else {
   156  		a.Content = nilContent
   157  	}
   158  	return nil
   159  }
   160  
   161  // UnmarshalJSON implements the [encoding/json.Unmarshaler] interface.
   162  func (a *Attachments) UnmarshalJSON(data []byte) error {
   163  	atts := make(map[string]*Attachment)
   164  	if err := json.Unmarshal(data, &atts); err != nil {
   165  		return err
   166  	}
   167  	for filename, att := range atts {
   168  		att.Filename = filename
   169  	}
   170  	*a = atts
   171  	return nil
   172  }
   173  
   174  // AttachmentsIterator allows reading streamed attachments from a multi-part
   175  // [DB.Get] request.
   176  type AttachmentsIterator struct {
   177  	atti    driver.Attachments
   178  	onClose func()
   179  }
   180  
   181  // Next returns the next attachment in the stream. [io.EOF] will be
   182  // returned when there are no more attachments.
   183  //
   184  // The returned attachment is only valid until the next call to [Next], or a
   185  // call to [Close].
   186  func (i *AttachmentsIterator) Next() (*Attachment, error) {
   187  	att := new(driver.Attachment)
   188  	if err := i.atti.Next(att); err != nil {
   189  		if err == io.EOF {
   190  			if e2 := i.Close(); e2 != nil {
   191  				return nil, e2
   192  			}
   193  		}
   194  		return nil, err
   195  	}
   196  	katt := Attachment(*att)
   197  	return &katt, nil
   198  }
   199  
   200  // Close closes the AttachmentsIterator. It is automatically called when
   201  // [AttachmentsIterator.Next] returns [io.EOF].
   202  func (i *AttachmentsIterator) Close() error {
   203  	if i.onClose != nil {
   204  		i.onClose()
   205  	}
   206  	return i.atti.Close()
   207  }
   208  
   209  // Iterator returns a function that can be used to iterate over the attachments.
   210  // This function works with Go 1.23's range functions, and is an alternative to
   211  // using [AttachmentsIterator.Next] directly.
   212  func (i *AttachmentsIterator) Iterator() func(yield func(*Attachment, error) bool) {
   213  	return func(yield func(*Attachment, error) bool) {
   214  		for {
   215  			att, err := i.Next()
   216  			if err == io.EOF {
   217  				return
   218  			}
   219  			if !yield(att, err) || err != nil {
   220  				_ = i.Close()
   221  				return
   222  			}
   223  		}
   224  	}
   225  }
   226  

View as plain text