Skip to content
File

Blob: src/workerd/api/r2-bucket.h

cpp587 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#pragma once
6 
7#include "r2-rpc.h"
8 
9#include <workerd/api/streams/readable.h>
10#include <workerd/jsg/jsg.h>
11 
12namespace workerd::api {
13class Headers;
14}
15 
16namespace workerd::api::public_beta {
17 
18struct StringTagParams {
19 kj::LiteralStringConst key;
20 kj::StringPtr value;
21};
22 
23struct R2UserTracing {
24 kj::LiteralStringConst op;
25 StringTagParams method;
26 // Passing Maybe<kj::StringPtr> instead of Maybe<StringTagParams> here – this avoids a branch on
27 // the caller side when bucket is already a Maybe, which is more convenient.
28 kj::Maybe<kj::StringPtr> bucket;
29 kj::Maybe<StringTagParams> extraTag;
30};
31 
32// Helper for creating R2 HTTP Client with the right span tags across operations. This is much
33// cleaner than setting span tags directly in each function.
34kj::Own<kj::HttpClient> r2GetClient(IoContext& context, uint subrequestChannel, R2UserTracing user);
35 
36kj::ArrayPtr<kj::StringPtr> fillR2Path(
37 kj::StringPtr pathStorage[1], const kj::Maybe<kj::String>& bucket);
38 
39class R2MultipartUpload;
40 
41// A capability to an R2 Bucket.
42class R2Bucket: public jsg::Object {
43 protected:
44 struct friend_tag_t {};
45 
46 struct FeatureFlags {
47 FeatureFlags(CompatibilityFlags::Reader featureFlags);
48 
49 bool listHonorsIncludes;
50 };
51 
52 public:
53 // `clientIndex` is what to pass to IoContext::getHttpClient() to get an HttpClient
54 // representing this namespace.
55 explicit R2Bucket(CompatibilityFlags::Reader featureFlags, uint clientIndex)
56 : featureFlags(featureFlags),
57 clientIndex(clientIndex) {}
58 
59 explicit R2Bucket(FeatureFlags featureFlags, uint clientIndex, kj::String bucket, friend_tag_t)
60 : featureFlags(featureFlags),
61 clientIndex(clientIndex),
62 adminBucket(kj::mv(bucket)) {}
63 
64 explicit R2Bucket(
65 FeatureFlags featureFlags, uint clientIndex, kj::String bucket, kj::String binding)
66 : featureFlags(featureFlags),
67 clientIndex(clientIndex),
68 bucket(kj::mv(bucket)),
69 binding(kj::mv(binding)) {}
70 
71 explicit R2Bucket(
72 FeatureFlags featureFlags, uint clientIndex, kj::String bucket, kj::String jwt, friend_tag_t)
73 : featureFlags(featureFlags),
74 clientIndex(clientIndex),
75 adminBucket(kj::mv(bucket)),
76 jwt(kj::mv(jwt)) {}
77 
78 struct Range {
79 jsg::Optional<double> offset;
80 jsg::Optional<double> length;
81 jsg::Optional<double> suffix;
82 
83 JSG_STRUCT(offset, length, suffix);
84 JSG_STRUCT_TS_OVERRIDE(type R2Range =
85 | { offset: number; length?: number }
86 | { offset?: number; length: number }
87 | { suffix: number }
88 );
89 };
90 
91 struct Conditional {
92 jsg::Optional<jsg::NonCoercible<kj::String>> etagMatches;
93 jsg::Optional<jsg::NonCoercible<kj::String>> etagDoesNotMatch;
94 jsg::Optional<kj::Date> uploadedBefore;
95 jsg::Optional<kj::Date> uploadedAfter;
96 jsg::Optional<bool> secondsGranularity;
97 
98 JSG_STRUCT(etagMatches, etagDoesNotMatch, uploadedBefore, uploadedAfter, secondsGranularity);
99 JSG_STRUCT_TS_OVERRIDE(R2Conditional);
100 };
101 
102 struct GetOptions {
103 jsg::Optional<kj::OneOf<Conditional, jsg::Ref<Headers>>> onlyIf;
104 jsg::Optional<kj::OneOf<Range, jsg::Ref<Headers>>> range;
105 jsg::Optional<kj::OneOf<kj::Array<byte>, kj::String>> ssecKey;
106 
107 JSG_STRUCT(onlyIf, range, ssecKey);
108 JSG_STRUCT_TS_OVERRIDE(R2GetOptions);
109 };
110 
111 struct StringChecksums {
112 jsg::Optional<kj::String> md5;
113 jsg::Optional<kj::String> sha1;
114 jsg::Optional<kj::String> sha256;
115 jsg::Optional<kj::String> sha384;
116 jsg::Optional<kj::String> sha512;
117 
118 JSG_STRUCT(md5, sha1, sha256, sha384, sha512);
119 JSG_STRUCT_TS_OVERRIDE(R2StringChecksums);
120 };
121 
122 class Checksums: public jsg::Object {
123 public:
124 Checksums(jsg::Optional<kj::Array<kj::byte>> md5,
125 jsg::Optional<kj::Array<kj::byte>> sha1,
126 jsg::Optional<kj::Array<kj::byte>> sha256,
127 jsg::Optional<kj::Array<kj::byte>> sha384,
128 jsg::Optional<kj::Array<kj::byte>> sha512)
129 : md5(kj::mv(md5)),
130 sha1(kj::mv(sha1)),
131 sha256(kj::mv(sha256)),
132 sha384(kj::mv(sha384)),
133 sha512(kj::mv(sha512)) {}
134 
135 jsg::Optional<jsg::JsArrayBuffer> getMd5(jsg::Lock& js);
136 jsg::Optional<jsg::JsArrayBuffer> getSha1(jsg::Lock& js);
137 jsg::Optional<jsg::JsArrayBuffer> getSha256(jsg::Lock& js);
138 jsg::Optional<jsg::JsArrayBuffer> getSha384(jsg::Lock& js);
139 jsg::Optional<jsg::JsArrayBuffer> getSha512(jsg::Lock& js);
140 
141 StringChecksums toJSON();
142 
143 JSG_RESOURCE_TYPE(Checksums) {
144 JSG_LAZY_READONLY_INSTANCE_PROPERTY(md5, getMd5);
145 JSG_LAZY_READONLY_INSTANCE_PROPERTY(sha1, getSha1);
146 JSG_LAZY_READONLY_INSTANCE_PROPERTY(sha256, getSha256);
147 JSG_LAZY_READONLY_INSTANCE_PROPERTY(sha384, getSha384);
148 JSG_LAZY_READONLY_INSTANCE_PROPERTY(sha512, getSha512);
149 JSG_METHOD(toJSON);
150 JSG_TS_OVERRIDE(R2Checksums {
151 readonly md5?: ArrayBuffer;
152 readonly sha1?: ArrayBuffer;
153 readonly sha256?: ArrayBuffer;
154 readonly sha384?: ArrayBuffer;
155 readonly sha512?: ArrayBuffer;
156 });
157 }
158 
159 jsg::Optional<kj::Array<kj::byte>> md5;
160 jsg::Optional<kj::Array<kj::byte>> sha1;
161 jsg::Optional<kj::Array<kj::byte>> sha256;
162 jsg::Optional<kj::Array<kj::byte>> sha384;
163 jsg::Optional<kj::Array<kj::byte>> sha512;
164 
165 void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
166 tracker.trackField("md5", md5);
167 tracker.trackField("sha1", sha1);
168 tracker.trackField("sha256", sha256);
169 tracker.trackField("sha384", sha384);
170 tracker.trackField("sha512", sha512);
171 }
172 };
173 
174 struct HttpMetadata {
175 static HttpMetadata fromRequestHeaders(jsg::Lock& js, Headers& h);
176 
177 jsg::Optional<kj::String> contentType;
178 jsg::Optional<kj::String> contentLanguage;
179 jsg::Optional<kj::String> contentDisposition;
180 jsg::Optional<kj::String> contentEncoding;
181 jsg::Optional<kj::String> cacheControl;
182 jsg::Optional<kj::Date> cacheExpiry;
183 
184 JSG_STRUCT(contentType,
185 contentLanguage,
186 contentDisposition,
187 contentEncoding,
188 cacheControl,
189 cacheExpiry);
190 JSG_STRUCT_TS_OVERRIDE(R2HTTPMetadata);
191 
192 HttpMetadata clone() const;
193 
194 JSG_MEMORY_INFO(HttpMetadata) {
195 tracker.trackField("contentType", contentType);
196 tracker.trackField("contentLanguage", contentLanguage);
197 tracker.trackField("contentDisposition", contentDisposition);
198 tracker.trackField("contentEncoding", contentEncoding);
199 tracker.trackField("cacheControl", cacheControl);
200 }
201 };
202 
203 struct PutOptions {
204 jsg::Optional<kj::OneOf<Conditional, jsg::Ref<Headers>>> onlyIf;
205 jsg::Optional<kj::OneOf<HttpMetadata, jsg::Ref<Headers>>> httpMetadata;
206 jsg::Optional<jsg::Dict<kj::String>> customMetadata;
207 jsg::Optional<kj::OneOf<jsg::JsRef<jsg::JsBufferSource>, jsg::NonCoercible<kj::String>>> md5;
208 jsg::Optional<kj::OneOf<jsg::JsRef<jsg::JsBufferSource>, jsg::NonCoercible<kj::String>>> sha1;
209 jsg::Optional<kj::OneOf<jsg::JsRef<jsg::JsBufferSource>, jsg::NonCoercible<kj::String>>> sha256;
210 jsg::Optional<kj::OneOf<jsg::JsRef<jsg::JsBufferSource>, jsg::NonCoercible<kj::String>>> sha384;
211 jsg::Optional<kj::OneOf<jsg::JsRef<jsg::JsBufferSource>, jsg::NonCoercible<kj::String>>> sha512;
212 jsg::Optional<kj::String> storageClass;
213 jsg::Optional<kj::OneOf<kj::Array<byte>, kj::String>> ssecKey;
214 
215 JSG_STRUCT(onlyIf,
216 httpMetadata,
217 customMetadata,
218 md5,
219 sha1,
220 sha256,
221 sha384,
222 sha512,
223 storageClass,
224 ssecKey);
225 JSG_STRUCT_TS_OVERRIDE(R2PutOptions);
226 };
227 
228 struct MultipartOptions {
229 jsg::Optional<kj::OneOf<HttpMetadata, jsg::Ref<Headers>>> httpMetadata;
230 jsg::Optional<jsg::Dict<kj::String>> customMetadata;
231 jsg::Optional<kj::String> storageClass;
232 jsg::Optional<kj::OneOf<kj::Array<byte>, kj::String>> ssecKey;
233 
234 JSG_STRUCT(httpMetadata, customMetadata, storageClass, ssecKey);
235 JSG_STRUCT_TS_OVERRIDE(R2MultipartOptions);
236 };
237 
238 class HeadResult: public jsg::Object {
239 public:
240 HeadResult(kj::String name,
241 kj::String version,
242 double size,
243 kj::String etag,
244 jsg::Ref<Checksums> checksums,
245 kj::Date uploaded,
246 jsg::Optional<HttpMetadata> httpMetadata,
247 jsg::Optional<jsg::Dict<kj::String>> customMetadata,
248 jsg::Optional<Range> range,
249 kj::String storageClass,
250 jsg::Optional<kj::String> ssecKeyMd5)
251 : name(kj::mv(name)),
252 version(kj::mv(version)),
253 size(size),
254 etag(kj::mv(etag)),
255 checksums(kj::mv(checksums)),
256 uploaded(uploaded),
257 httpMetadata(kj::mv(httpMetadata)),
258 customMetadata(kj::mv(customMetadata)),
259 range(kj::mv(range)),
260 storageClass(kj::mv(storageClass)),
261 ssecKeyMd5(kj::mv(ssecKeyMd5)) {}
262 
263 kj::StringPtr getName() const {
264 return name;
265 }
266 kj::StringPtr getVersion() const {
267 return version;
268 }
269 double getSize() const {
270 return size;
271 }
272 kj::StringPtr getEtag() const {
273 return etag;
274 }
275 kj::String getHttpEtag() const {
276 return kj::str('"', etag, '"');
277 }
278 jsg::Ref<Checksums> getChecksums() {
279 return checksums.addRef();
280 }
281 kj::Date getUploaded() const {
282 return uploaded;
283 }
284 kj::StringPtr getStorageClass() const {
285 return storageClass;
286 }
287 jsg::Optional<kj::StringPtr> getSSECKeyMd5() const {
288 return ssecKeyMd5;
289 }
290 
291 jsg::Optional<HttpMetadata> getHttpMetadata() const {
292 return httpMetadata.map([](const HttpMetadata& m) { return m.clone(); });
293 }
294 
295 const jsg::Optional<jsg::Dict<kj::String>> getCustomMetadata() const {
296 return customMetadata.map([](const jsg::Dict<kj::String>& m) {
297 return jsg::Dict<kj::String>{
298 .fields =
299 KJ_MAP(f, m.fields) {
300 return jsg::Dict<kj::String>::Field{.name = kj::str(f.name), .value = kj::str(f.value)};
301 },
302 };
303 });
304 }
305 
306 jsg::Optional<Range> getRange() {
307 return range;
308 }
309 
310 void writeHttpMetadata(jsg::Lock& js, Headers& headers);
311 
312 JSG_RESOURCE_TYPE(HeadResult) {
313 JSG_LAZY_READONLY_INSTANCE_PROPERTY(key, getName);
314 JSG_LAZY_READONLY_INSTANCE_PROPERTY(version, getVersion);
315 JSG_LAZY_READONLY_INSTANCE_PROPERTY(size, getSize);
316 JSG_LAZY_READONLY_INSTANCE_PROPERTY(etag, getEtag);
317 JSG_LAZY_READONLY_INSTANCE_PROPERTY(httpEtag, getHttpEtag);
318 JSG_LAZY_READONLY_INSTANCE_PROPERTY(checksums, getChecksums);
319 JSG_LAZY_READONLY_INSTANCE_PROPERTY(uploaded, getUploaded);
320 JSG_LAZY_READONLY_INSTANCE_PROPERTY(httpMetadata, getHttpMetadata);
321 JSG_LAZY_READONLY_INSTANCE_PROPERTY(customMetadata, getCustomMetadata);
322 JSG_LAZY_READONLY_INSTANCE_PROPERTY(range, getRange);
323 JSG_LAZY_READONLY_INSTANCE_PROPERTY(storageClass, getStorageClass);
324 JSG_LAZY_READONLY_INSTANCE_PROPERTY(ssecKeyMd5, getSSECKeyMd5);
325 JSG_METHOD(writeHttpMetadata);
326 JSG_TS_OVERRIDE(R2Object);
327 }
328 
329 void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
330 tracker.trackField("name", name);
331 tracker.trackField("version", version);
332 tracker.trackField("etag", etag);
333 tracker.trackField("checksums", checksums);
334 tracker.trackField("httpMetadata", httpMetadata);
335 tracker.trackField("customMetadata", customMetadata);
336 tracker.trackField("ssecKeyMd5", ssecKeyMd5);
337 }
338 
339 protected:
340 kj::String name;
341 kj::String version;
342 double size;
343 kj::String etag;
344 jsg::Ref<Checksums> checksums;
345 kj::Date uploaded;
346 jsg::Optional<HttpMetadata> httpMetadata;
347 jsg::Optional<jsg::Dict<kj::String>> customMetadata;
348 
349 jsg::Optional<Range> range;
350 kj::String storageClass;
351 jsg::Optional<kj::String> ssecKeyMd5;
352 friend class R2Bucket;
353 };
354 
355 class GetResult: public HeadResult {
356 public:
357 GetResult(kj::String name,
358 kj::String version,
359 double size,
360 kj::String etag,
361 jsg::Ref<Checksums> checksums,
362 kj::Date uploaded,
363 jsg::Optional<HttpMetadata> httpMetadata,
364 jsg::Optional<jsg::Dict<kj::String>> customMetadata,
365 jsg::Optional<Range> range,
366 kj::String storageClass,
367 jsg::Optional<kj::String> ssecKeyMd5,
368 jsg::Ref<ReadableStream> body)
369 : HeadResult(kj::mv(name),
370 kj::mv(version),
371 size,
372 kj::mv(etag),
373 kj::mv(checksums),
374 uploaded,
375 kj::mv(KJ_ASSERT_NONNULL(httpMetadata)),
376 kj::mv(KJ_ASSERT_NONNULL(customMetadata)),
377 range,
378 kj::mv(storageClass),
379 kj::mv(ssecKeyMd5)),
380 body(kj::mv(body)) {}
381 
382 jsg::Ref<ReadableStream> getBody() {
383 return body.addRef();
384 }
385 
386 bool getBodyUsed() {
387 return body->isDisturbed();
388 }
389 
390 jsg::Promise<jsg::BufferSource> arrayBuffer(jsg::Lock& js);
391 jsg::Promise<jsg::BufferSource> bytes(jsg::Lock& js);
392 jsg::Promise<kj::String> text(jsg::Lock& js);
393 jsg::Promise<jsg::Value> json(jsg::Lock& js);
394 jsg::Promise<jsg::Ref<Blob>> blob(jsg::Lock& js);
395 
396 JSG_RESOURCE_TYPE(GetResult) {
397 JSG_INHERIT(HeadResult);
398 JSG_READONLY_PROTOTYPE_PROPERTY(body, getBody);
399 JSG_READONLY_PROTOTYPE_PROPERTY(bodyUsed, getBodyUsed);
400 JSG_METHOD(arrayBuffer);
401 JSG_METHOD(bytes);
402 JSG_METHOD(text);
403 JSG_METHOD(json);
404 JSG_METHOD(blob);
405 JSG_TS_OVERRIDE(R2ObjectBody {
406 json<T>(): Promise<T>;
407 bytes(): Promise<Uint8Array>;
408 arrayBuffer(): Promise<ArrayBuffer>;
409 });
410 }
411 
412 void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
413 tracker.trackField("body", body);
414 }
415 
416 private:
417 jsg::Ref<ReadableStream> body;
418 };
419 
420 struct ListResult {
421 kj::Array<jsg::Ref<HeadResult>> objects;
422 bool truncated;
423 jsg::Optional<kj::String> cursor;
424 kj::Array<kj::String> delimitedPrefixes;
425 
426 JSG_STRUCT(objects, truncated, cursor, delimitedPrefixes);
427 JSG_STRUCT_TS_OVERRIDE(type R2Objects = {
428 objects: R2Object[];
429 delimitedPrefixes: string[];
430 } & (
431 | { truncated: true; cursor: string }
432 | { truncated: false }
433 ));
434 };
435 
436 struct ListOptions {
437 jsg::Optional<int> limit;
438 jsg::Optional<jsg::NonCoercible<kj::String>> prefix;
439 jsg::Optional<jsg::NonCoercible<kj::String>> cursor;
440 jsg::Optional<jsg::NonCoercible<kj::String>> delimiter;
441 jsg::Optional<jsg::NonCoercible<kj::String>> startAfter;
442 jsg::Optional<kj::Array<jsg::NonCoercible<kj::String>>> include;
443 
444 JSG_STRUCT(limit, prefix, cursor, delimiter, startAfter, include);
445 JSG_STRUCT_TS_OVERRIDE(type R2ListOptions = never);
446 // Delete the auto-generated ListOptions definition, we instead define it
447 // with R2Bucket so we can access compatibility flags. Note, even though
448 // we're deleting the definition, all definitions will still be renamed
449 // from `R2BucketListOptions` to `R2ListOptions`.
450 };
451 
452 jsg::Promise<kj::Maybe<jsg::Ref<HeadResult>>> head(jsg::Lock& js,
453 kj::String key,
454 const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType,
455 CompatibilityFlags::Reader flags);
456 jsg::Promise<kj::OneOf<kj::Maybe<jsg::Ref<GetResult>>, jsg::Ref<HeadResult>>> get(jsg::Lock& js,
457 kj::String key,
458 jsg::Optional<GetOptions> options,
459 const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType,
460 CompatibilityFlags::Reader flags);
461 jsg::Promise<kj::Maybe<jsg::Ref<HeadResult>>> put(jsg::Lock& js,
462 kj::String key,
463 kj::Maybe<R2PutValue> value,
464 jsg::Optional<PutOptions> options,
465 const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);
466 jsg::Promise<jsg::Ref<R2MultipartUpload>> createMultipartUpload(jsg::Lock& js,
467 kj::String key,
468 jsg::Optional<MultipartOptions> options,
469 const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);
470 jsg::Ref<R2MultipartUpload> resumeMultipartUpload(jsg::Lock& js,
471 kj::String key,
472 kj::String uploadId,
473 const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);
474 jsg::Promise<void> delete_(jsg::Lock& js,
475 kj::OneOf<kj::String, kj::Array<kj::String>> keys,
476 const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);
477 jsg::Promise<ListResult> list(jsg::Lock& js,
478 jsg::Optional<ListOptions> options,
479 const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType,
480 CompatibilityFlags::Reader flags);
481 
482 JSG_RESOURCE_TYPE(R2Bucket, CompatibilityFlags::Reader flags) {
483 JSG_METHOD(head);
484 JSG_METHOD(get);
485 JSG_METHOD(put);
486 JSG_METHOD(createMultipartUpload);
487 JSG_METHOD(resumeMultipartUpload);
488 JSG_METHOD_NAMED(delete, delete_);
489 JSG_METHOD(list);
490 
491 JSG_TS_ROOT();
492 JSG_TS_OVERRIDE({
493 // The order of these matters, since typescript tries to match function signatures in order
494 get(key: string, options: R2GetOptions & { onlyIf: R2BucketConditional | Headers }): Promise<R2ObjectBody | R2Object | null>;
495 get(key: string, options?: R2GetOptions): Promise<R2ObjectBody | null>;
496 
497 put(
498 key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob,
499 options?: R2PutOptions & { onlyIf: R2BucketConditional | Headers }
500 ): Promise<R2Object | null>;
501 put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, options?: R2PutOptions): Promise<R2Object>;
502 });
503 // Exclude `R2Object` from `get` return type if `onlyIf` not specified, and exclude `null` from `put` return type
504 
505 // Rather than using the auto-generated R2ListOptions definition, we define
506 // it here so we can access compatibility flags from JSG_RESOURCE_TYPE.
507 if (flags.getR2ListHonorIncludeFields()) {
508 JSG_TS_DEFINE(interface R2ListOptions {
509 limit?: number;
510 prefix?: string;
511 cursor?: string;
512 delimiter?: string;
513 startAfter?: string;
514 include?: ("httpMetadata" | "customMetadata")[];
515 });
516 } else {
517 JSG_TS_DEFINE(interface R2ListOptions {
518 limit?: number;
519 prefix?: string;
520 cursor?: string;
521 delimiter?: string;
522 startAfter?: string;
523 });
524 // Omit `include` field if compatibility flag disabled as ignored
525 }
526 }
527 
528 struct WildcardEtag {};
529 struct WeakEtag {
530 kj::String value;
531 };
532 struct StrongEtag {
533 kj::String value;
534 };
535 using Etag = kj::OneOf<WildcardEtag, WeakEtag, StrongEtag>;
536 
537 struct UnwrappedConditional {
538 UnwrappedConditional(jsg::Lock& js, Headers& h);
539 UnwrappedConditional(const Conditional& c);
540 
541 kj::Maybe<kj::Array<Etag>> etagMatches;
542 kj::Maybe<kj::Array<Etag>> etagDoesNotMatch;
543 kj::Maybe<kj::Date> uploadedBefore;
544 kj::Maybe<kj::Date> uploadedAfter;
545 bool secondsGranularity = false;
546 };
547 
548 void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
549 tracker.trackField("adminBucket", adminBucket);
550 tracker.trackField("jwt", jwt);
551 }
552 
553 protected:
554 kj::Maybe<kj::StringPtr> adminBucketName() const {
555 return adminBucket;
556 }
557 
558 kj::Maybe<kj::StringPtr> bucketName() const {
559 return bucket;
560 }
561 
562 kj::Maybe<kj::StringPtr> bindingName() const {
563 return binding;
564 }
565 
566 private:
567 FeatureFlags featureFlags;
568 uint clientIndex;
569 kj::Maybe<kj::String> adminBucket;
570 kj::Maybe<kj::String> bucket;
571 kj::Maybe<kj::String> binding;
572 kj::Maybe<kj::String> jwt;
573 
574 friend class R2MultipartUpload;
575};
576 
577// Non-generic wrapper avoid moving the parseObjectMetadata implementation into this header file
578// by making use of dynamic dispatch.
579kj::Maybe<jsg::Ref<R2Bucket::HeadResult>> parseHeadResultWrapper(jsg::Lock& js,
580 kj::StringPtr action,
581 R2Result& r2Result,
582 const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);
583 
584void addHeadResultSpanTags(jsg::Lock& js, TraceContext& traceContext, R2Bucket::HeadResult& result);
585 
586} // namespace workerd::api::public_beta