1
2
3
4
5
6
7
8
9
10
11
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 }
166
View as plain text