github.com/go-kivik/kivik/v4@v4.3.2/couchdb/attachments.go (about) 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 couchdb 14 15 import ( 16 "context" 17 "errors" 18 "net/http" 19 20 "github.com/go-kivik/kivik/v4/couchdb/chttp" 21 "github.com/go-kivik/kivik/v4/driver" 22 internal "github.com/go-kivik/kivik/v4/int/errors" 23 ) 24 25 func (d *db) PutAttachment(ctx context.Context, docID string, att *driver.Attachment, options driver.Options) (newRev string, err error) { 26 if docID == "" { 27 return "", missingArg("docID") 28 } 29 if att == nil { 30 return "", missingArg("att") 31 } 32 if att.Filename == "" { 33 return "", missingArg("att.Filename") 34 } 35 if att.Content == nil { 36 return "", missingArg("att.Content") 37 } 38 39 chttpOpts := chttp.NewOptions(options) 40 41 opts := map[string]interface{}{} 42 options.Apply(opts) 43 query, err := optionsToParams(opts) 44 if err != nil { 45 return "", err 46 } 47 var response struct { 48 Rev string `json:"rev"` 49 } 50 chttpOpts.Body = att.Content 51 chttpOpts.ContentType = att.ContentType 52 chttpOpts.Query = query 53 err = d.Client.DoJSON(ctx, http.MethodPut, d.path(chttp.EncodeDocID(docID)+"/"+att.Filename), chttpOpts, &response) 54 if err != nil { 55 return "", err 56 } 57 return response.Rev, nil 58 } 59 60 func (d *db) GetAttachmentMeta(ctx context.Context, docID, filename string, options driver.Options) (*driver.Attachment, error) { 61 resp, err := d.fetchAttachment(ctx, http.MethodHead, docID, filename, options) 62 if err != nil { 63 return nil, err 64 } 65 att, err := decodeAttachment(resp) 66 return att, err 67 } 68 69 func (d *db) GetAttachment(ctx context.Context, docID, filename string, options driver.Options) (*driver.Attachment, error) { 70 resp, err := d.fetchAttachment(ctx, http.MethodGet, docID, filename, options) 71 if err != nil { 72 return nil, err 73 } 74 return decodeAttachment(resp) 75 } 76 77 func (d *db) fetchAttachment(ctx context.Context, method, docID, filename string, options driver.Options) (*http.Response, error) { 78 if method == "" { 79 return nil, errors.New("method required") 80 } 81 if docID == "" { 82 return nil, missingArg("docID") 83 } 84 if filename == "" { 85 return nil, missingArg("filename") 86 } 87 chttpOpts := chttp.NewOptions(options) 88 89 opts := map[string]interface{}{} 90 options.Apply(opts) 91 var err error 92 chttpOpts.Query, err = optionsToParams(opts) 93 if err != nil { 94 return nil, err 95 } 96 resp, err := d.Client.DoReq(ctx, method, d.path(chttp.EncodeDocID(docID)+"/"+filename), chttpOpts) 97 if err != nil { 98 return nil, err 99 } 100 return resp, chttp.ResponseError(resp) 101 } 102 103 func decodeAttachment(resp *http.Response) (*driver.Attachment, error) { 104 cType, err := getContentType(resp) 105 if err != nil { 106 return nil, err 107 } 108 digest, err := getDigest(resp) 109 if err != nil { 110 return nil, err 111 } 112 113 return &driver.Attachment{ 114 ContentType: cType, 115 Digest: digest, 116 Size: resp.ContentLength, 117 Content: resp.Body, 118 }, nil 119 } 120 121 func getContentType(resp *http.Response) (string, error) { 122 ctype := resp.Header.Get("Content-Type") 123 if _, ok := resp.Header["Content-Type"]; !ok { 124 return "", &internal.Error{Status: http.StatusBadGateway, Err: errors.New("no Content-Type in response")} 125 } 126 return ctype, nil 127 } 128 129 func getDigest(resp *http.Response) (string, error) { 130 etag, ok := chttp.ETag(resp) 131 if !ok { 132 return "", &internal.Error{Status: http.StatusBadGateway, Err: errors.New("ETag header not found")} 133 } 134 return etag, nil 135 } 136 137 func (d *db) DeleteAttachment(ctx context.Context, docID, filename string, options driver.Options) (newRev string, err error) { 138 if docID == "" { 139 return "", missingArg("docID") 140 } 141 opts := map[string]interface{}{} 142 options.Apply(opts) 143 if rev, _ := opts["rev"].(string); rev == "" { 144 return "", missingArg("rev") 145 } 146 if filename == "" { 147 return "", missingArg("filename") 148 } 149 150 chttpOpts := chttp.NewOptions(options) 151 152 chttpOpts.Query, err = optionsToParams(opts) 153 if err != nil { 154 return "", err 155 } 156 var response struct { 157 Rev string `json:"rev"` 158 } 159 160 err = d.Client.DoJSON(ctx, http.MethodDelete, d.path(chttp.EncodeDocID(docID)+"/"+filename), chttpOpts, &response) 161 if err != nil { 162 return "", err 163 } 164 return response.Rev, nil 165 }