Skip to content
File

Blob: src/workerd/jsg/value.h

cpp1491 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// INTERNAL IMPLEMENTATION FILE
7//
8// Handling of various basic value types: numbers, booleans, strings, optionals, maybes, variants,
9// arrays, buffers, dicts.
10 
11#include <workerd/jsg/fast-api.h>
12#include <workerd/jsg/util.h>
13#include <workerd/jsg/web-idl.h>
14#include <workerd/jsg/wrappable.h>
15 
16#include <v8-container.h>
17#include <v8-date.h>
18 
19#include <kj/debug.h>
20#include <kj/one-of.h>
21#include <kj/time.h>
22 
23namespace workerd::jsg {
24 
25// =======================================================================================
26// Primitives (numbers, booleans)
27// TypeWrapper mixin for numbers and booleans.
28//
29// This wrapper has extra wrap() overloads that take an isolate instead of a
30// lock and a context. This is used to implement static constants in
31// JavaScript: we need to be able to wrap C++ constants in V8 values before a
32// context has been entered.
33//
34// Note that we can't generally change the wrap(js, context, ...) functions to wrap(isolate, ...)
35// because ResourceWrapper<TW, T>::wrap() needs the context to create new object instances.
36class PrimitiveWrapper {
37 public:
38 static constexpr const char* getName(double*) {
39 return "number";
40 }
41 
42 v8::Local<v8::Number> wrap(Lock& js,
43 v8::Local<v8::Context> context,
44 kj::Maybe<v8::Local<v8::Object>> creator,
45 double value) {
46 return wrap(js.v8Isolate, creator, value);
47 }
48 
49 v8::Local<v8::Number> wrap(
50 v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, double value) {
51 return v8::Number::New(isolate, value);
52 }
53 
54 kj::Maybe<double> tryUnwrap(Lock& js,
55 v8::Local<v8::Context> context,
56 v8::Local<v8::Value> handle,
57 double*,
58 kj::Maybe<v8::Local<v8::Object>> parentObject) {
59 return check(handle->ToNumber(context))->Value();
60 }
61 
62 static constexpr const char* getName(int8_t*) {
63 return "byte";
64 }
65 
66 v8::Local<v8::Number> wrap(Lock& js,
67 v8::Local<v8::Context> context,
68 kj::Maybe<v8::Local<v8::Object>> creator,
69 int8_t value) {
70 return wrap(js.v8Isolate, creator, value);
71 }
72 
73 v8::Local<v8::Number> wrap(
74 v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, int8_t value) {
75 return v8::Integer::New(isolate, value);
76 }
77 
78 kj::Maybe<int8_t> tryUnwrap(Lock& js,
79 v8::Local<v8::Context> context,
80 v8::Local<v8::Value> handle,
81 int8_t*,
82 kj::Maybe<v8::Local<v8::Object>> parentObject) {
83 auto value = check(handle->ToNumber(context))->Value();
84 
85 JSG_REQUIRE(
86 isFinite(value), TypeError, "The value cannot be converted because it is not an integer.");
87 
88 JSG_REQUIRE(
89 value <= static_cast<int8_t>(kj::maxValue) && value >= static_cast<int8_t>(kj::minValue),
90 TypeError,
91 kj::str("Value out of range. Must be between ", static_cast<int8_t>(kj::minValue), " and ",
92 static_cast<int8_t>(kj::maxValue), " (inclusive)."));
93 
94 return static_cast<int8_t>(value);
95 }
96 
97 static constexpr const char* getName(uint8_t*) {
98 return "octet";
99 }
100 
101 v8::Local<v8::Number> wrap(Lock& js,
102 v8::Local<v8::Context> context,
103 kj::Maybe<v8::Local<v8::Object>> creator,
104 uint8_t value) {
105 return wrap(js.v8Isolate, creator, value);
106 }
107 
108 v8::Local<v8::Number> wrap(
109 v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, uint8_t value) {
110 return v8::Integer::NewFromUnsigned(isolate, value);
111 }
112 
113 kj::Maybe<uint8_t> tryUnwrap(Lock& js,
114 v8::Local<v8::Context> context,
115 v8::Local<v8::Value> handle,
116 uint8_t*,
117 kj::Maybe<v8::Local<v8::Object>> parentObject) {
118 auto value = check(handle->ToNumber(context))->Value();
119 JSG_REQUIRE(
120 isFinite(value), TypeError, "The value cannot be converted because it is not an integer.");
121 
122 JSG_REQUIRE(value >= 0, TypeError,
123 "The value cannot be converted because it is negative and this "
124 "API expects a positive number.");
125 
126 JSG_REQUIRE(value <= static_cast<uint8_t>(kj::maxValue), TypeError,
127 kj::str("Value out of range. Must be less than or equal to ",
128 static_cast<uint8_t>(kj::maxValue), "."));
129 
130 return static_cast<uint8_t>(value);
131 }
132 
133 static constexpr const char* getName(int16_t*) {
134 return "short integer";
135 }
136 
137 v8::Local<v8::Number> wrap(Lock& js,
138 v8::Local<v8::Context> context,
139 kj::Maybe<v8::Local<v8::Object>> creator,
140 int16_t value) {
141 return wrap(js.v8Isolate, creator, value);
142 }
143 
144 v8::Local<v8::Number> wrap(
145 v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, int16_t value) {
146 return v8::Number::New(isolate, value);
147 }
148 
149 kj::Maybe<int16_t> tryUnwrap(Lock& js,
150 v8::Local<v8::Context> context,
151 v8::Local<v8::Value> handle,
152 int16_t*,
153 kj::Maybe<v8::Local<v8::Object>> parentObject) {
154 auto value = check(handle->ToNumber(context))->Value();
155 
156 JSG_REQUIRE(
157 isFinite(value), TypeError, "The value cannot be converted because it is not an integer.");
158 
159 JSG_REQUIRE(
160 value <= static_cast<int16_t>(kj::maxValue) && value >= static_cast<int16_t>(kj::minValue),
161 TypeError,
162 kj::str("Value out of range. Must be between ", static_cast<int16_t>(kj::minValue), " and ",
163 static_cast<int16_t>(kj::maxValue), " (inclusive)."));
164 
165 return static_cast<int16_t>(value);
166 }
167 
168 static constexpr const char* getName(uint16_t*) {
169 return "unsigned short integer";
170 }
171 
172 v8::Local<v8::Number> wrap(Lock& js,
173 v8::Local<v8::Context> context,
174 kj::Maybe<v8::Local<v8::Object>> creator,
175 uint16_t value) {
176 return wrap(js.v8Isolate, creator, value);
177 }
178 
179 v8::Local<v8::Number> wrap(
180 v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, uint16_t value) {
181 return v8::Integer::NewFromUnsigned(isolate, value);
182 }
183 
184 kj::Maybe<uint16_t> tryUnwrap(Lock& js,
185 v8::Local<v8::Context> context,
186 v8::Local<v8::Value> handle,
187 uint16_t*,
188 kj::Maybe<v8::Local<v8::Object>> parentObject) {
189 auto value = check(handle->ToNumber(context))->Value();
190 JSG_REQUIRE(
191 isFinite(value), TypeError, "The value cannot be converted because it is not an integer.");
192 
193 JSG_REQUIRE(value >= 0, TypeError,
194 "The value cannot be converted because it is negative and this "
195 "API expects a positive number.");
196 
197 JSG_REQUIRE(value <= static_cast<uint16_t>(kj::maxValue), TypeError,
198 kj::str("Value out of range. Must be less than or equal to ",
199 static_cast<uint16_t>(kj::maxValue), "."));
200 
201 return static_cast<uint16_t>(value);
202 }
203 
204 static constexpr const char* getName(int*) {
205 return "integer";
206 }
207 
208 v8::Local<v8::Number> wrap(Lock& js,
209 v8::Local<v8::Context> context,
210 kj::Maybe<v8::Local<v8::Object>> creator,
211 int value) {
212 return wrap(js.v8Isolate, creator, value);
213 }
214 
215 v8::Local<v8::Number> wrap(
216 v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, int value) {
217 return v8::Number::New(isolate, value);
218 }
219 
220 kj::Maybe<int> tryUnwrap(Lock& js,
221 v8::Local<v8::Context> context,
222 v8::Local<v8::Value> handle,
223 int*,
224 kj::Maybe<v8::Local<v8::Object>> parentObject) {
225 if (int num; handle->IsInt32() && handle->Int32Value(context).To(&num)) {
226 return num;
227 }
228 
229 auto value = check(handle->ToNumber(context))->Value();
230 if (!isFinite(value)) {
231 return 0;
232 }
233 
234 // One would think that RangeError is more appropriate than TypeError,
235 // but WebIDL says it should be TypeError.
236 JSG_REQUIRE(value <= static_cast<int>(kj::maxValue) && value >= static_cast<int>(kj::minValue),
237 TypeError,
238 kj::str("Value out of range. Must be between ", static_cast<int>(kj::minValue), " and ",
239 static_cast<int>(kj::maxValue), " (inclusive)."));
240 
241 return static_cast<int>(value);
242 }
243 
244 static constexpr const char* getName(uint32_t*) {
245 return "unsigned integer";
246 }
247 
248 v8::Local<v8::Number> wrap(Lock& js,
249 v8::Local<v8::Context> context,
250 kj::Maybe<v8::Local<v8::Object>> creator,
251 uint32_t value) {
252 return wrap(js.v8Isolate, creator, value);
253 }
254 
255 v8::Local<v8::Number> wrap(
256 v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, uint32_t value) {
257 return v8::Integer::NewFromUnsigned(isolate, value);
258 }
259 
260 kj::Maybe<uint32_t> tryUnwrap(Lock& js,
261 v8::Local<v8::Context> context,
262 v8::Local<v8::Value> handle,
263 uint32_t*,
264 kj::Maybe<v8::Local<v8::Object>> parentObject) {
265 if (uint32_t num; handle->IsUint32() && handle->Uint32Value(context).To(&num)) {
266 return num;
267 }
268 
269 auto value = check(handle->ToNumber(context))->Value();
270 JSG_REQUIRE(
271 isFinite(value), TypeError, "The value cannot be converted because it is not an integer.");
272 
273 JSG_REQUIRE(value >= 0, TypeError,
274 "The value cannot be converted because it is negative and this "
275 "API expects a positive number.");
276 
277 JSG_REQUIRE(value <= static_cast<uint32_t>(kj::maxValue), TypeError,
278 kj::str("Value out of range. Must be less than or equal to ",
279 static_cast<uint32_t>(kj::maxValue), "."));
280 
281 return static_cast<uint32_t>(value);
282 }
283 
284 static constexpr const char* getName(uint64_t*) {
285 return "bigint";
286 }
287 
288 v8::Local<v8::BigInt> wrap(Lock& js,
289 v8::Local<v8::Context> context,
290 kj::Maybe<v8::Local<v8::Object>> creator,
291 uint64_t value) {
292 return wrap(js.v8Isolate, creator, value);
293 }
294 
295 v8::Local<v8::BigInt> wrap(
296 v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, uint64_t value) {
297 return v8::BigInt::New(isolate, value);
298 }
299 
300 kj::Maybe<uint64_t> tryUnwrap(Lock& js,
301 v8::Local<v8::Context> context,
302 v8::Local<v8::Value> handle,
303 uint64_t*,
304 kj::Maybe<v8::Local<v8::Object>> parentObject) {
305 if (v8::Local<v8::BigInt> bigint;
306 handle->IsBigInt() && handle->ToBigInt(context).ToLocal(&bigint)) {
307 bool lossless;
308 auto value = bigint->Uint64Value(&lossless);
309 JSG_REQUIRE(lossless, TypeError,
310 "The value cannot be converted because it is either negative and this "
311 "API expects a positive bigint, or the value would be truncated.");
312 return value;
313 }
314 
315 auto value = check(handle->ToNumber(context))->Value();
316 JSG_REQUIRE(
317 isFinite(value), TypeError, "The value cannot be converted because it is not an integer.");
318 
319 JSG_REQUIRE(value >= 0, TypeError,
320 "The value cannot be converted because it is negative and this "
321 "API expects a positive bigint.");
322 
323 JSG_REQUIRE(value <= static_cast<uint64_t>(kj::maxValue), TypeError,
324 kj::str("Value out of range. Must be less than or equal to ",
325 static_cast<uint64_t>(kj::maxValue), "."));
326 
327 return static_cast<uint64_t>(value);
328 }
329 
330 static constexpr const char* getName(int64_t*) {
331 return "bigint";
332 }
333 
334 v8::Local<v8::BigInt> wrap(Lock& js,
335 v8::Local<v8::Context> context,
336 kj::Maybe<v8::Local<v8::Object>> creator,
337 int64_t value) {
338 return wrap(js.v8Isolate, creator, value);
339 }
340 
341 v8::Local<v8::BigInt> wrap(
342 v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, int64_t value) {
343 return v8::BigInt::New(isolate, value);
344 }
345 
346 kj::Maybe<int64_t> tryUnwrap(Lock& js,
347 v8::Local<v8::Context> context,
348 v8::Local<v8::Value> handle,
349 int64_t*,
350 kj::Maybe<v8::Local<v8::Object>> parentObject) {
351 if (v8::Local<v8::BigInt> bigint;
352 handle->IsBigInt() && handle->ToBigInt(context).ToLocal(&bigint)) {
353 bool lossless;
354 auto value = bigint->Int64Value(&lossless);
355 JSG_REQUIRE(
356 lossless, TypeError, "The value cannot be converted because it would be truncated.");
357 return value;
358 }
359 
360 auto value = check(handle->ToNumber(context))->Value();
361 JSG_REQUIRE(
362 isFinite(value), TypeError, "The value cannot be converted because it is not an integer.");
363 
364 JSG_REQUIRE(
365 value <= static_cast<int64_t>(kj::maxValue) && value >= static_cast<int64_t>(kj::minValue),
366 TypeError,
367 kj::str("Value out of range. Must be between ", static_cast<int64_t>(kj::minValue), " and ",
368 static_cast<int64_t>(kj::maxValue), " (inclusive)."));
369 
370 return static_cast<int64_t>(value);
371 }
372 
373 static constexpr const char* getName(bool*) {
374 return "boolean";
375 }
376 
377 template <StrictlyBool T>
378 v8::Local<v8::Boolean> wrap(
379 Lock& js, v8::Local<v8::Context> context, kj::Maybe<v8::Local<v8::Object>> creator, T value) {
380 // The template is needed to prevent this overload from being chosen for arbitrary types that
381 // can convert to bool, such as pointers.
382 return wrap(js.v8Isolate, creator, value);
383 }
384 
385 template <StrictlyBool T>
386 v8::Local<v8::Boolean> wrap(
387 v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, T value) {
388 // The template is needed to prevent this overload from being chosen for arbitrary types that
389 // can convert to bool, such as pointers.
390 return v8::Boolean::New(isolate, value);
391 }
392 
393 kj::Maybe<bool> tryUnwrap(Lock& js,
394 v8::Local<v8::Context> context,
395 v8::Local<v8::Value> handle,
396 bool*,
397 kj::Maybe<v8::Local<v8::Object>> parentObject) {
398 return handle->ToBoolean(js.v8Isolate)->Value();
399 }
400};
401 
402// =======================================================================================
403// Name
404class NameWrapper {
405 public:
406 static constexpr const char* getName(Name*) {
407 return "string or Symbol";
408 }
409 
410 v8::Local<v8::Value> wrap(Lock& js,
411 v8::Local<v8::Context> context,
412 kj::Maybe<v8::Local<v8::Object>> creator,
413 Name value) {
414 KJ_SWITCH_ONEOF(value.getUnwrapped(js.v8Isolate)) {
415 KJ_CASE_ONEOF(string, kj::StringPtr) {
416 return js.str(string);
417 }
418 KJ_CASE_ONEOF(symbol, v8::Local<v8::Symbol>) {
419 return symbol;
420 }
421 }
422 KJ_UNREACHABLE;
423 }
424 
425 kj::Maybe<Name> tryUnwrap(Lock& js,
426 v8::Local<v8::Context> context,
427 v8::Local<v8::Value> handle,
428 Name*,
429 kj::Maybe<v8::Local<v8::Object>> parentObject) {
430 if (handle->IsSymbol()) {
431 return Name(js, handle.As<v8::Symbol>());
432 }
433 
434 // Since most things are coercible to a string, this ought to catch pretty much
435 // any value other than symbol
436 return Name(js.toString(handle));
437 }
438};
439 
440// =======================================================================================
441// Strings
442 
443// TypeWrapper mixin for strings.
444//
445// This wrapper has an extra wrap() overload that takes an isolate instead of a context, for the
446// same reason discussed in PrimitiveWrapper.
447class StringWrapper {
448 public:
449 // TODO(someday): The conversion to kj::String doesn't explicitly consider the distinction
450 // between DOMString (~ WTF-8; could contain invalid code points) and USVString (invalid code
451 // points are always replaced with U+FFFD). Code should make an explict choice between the two.
452 
453 template <typename T>
454 requires(kj::isSameType<T, kj::ArrayPtr<const char>>() ||
455 kj::isSameType<T, kj::Array<const char>>() || kj::isSameType<T, kj::String>())
456 static constexpr const char* getName(T*) {
457 return "string";
458 }
459 
460 template <typename T>
461 requires(kj::isSameType<T, USVString>() || kj::isSameType<T, DOMString>())
462 static constexpr const char* getName(T*) {
463 if constexpr (kj::isSameType<T, USVString>()) {
464 return "USVString";
465 } else {
466 static_assert(kj::isSameType<T, DOMString>());
467 return "DOMString";
468 }
469 }
470 
471 v8::Local<v8::String> wrap(
472 v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, kj::StringPtr value) {
473 return v8Str(isolate, value);
474 }
475 
476 v8::Local<v8::String> wrap(Lock& js,
477 v8::Local<v8::Context> context,
478 kj::Maybe<v8::Local<v8::Object>> creator,
479 kj::ArrayPtr<const char> value) {
480 return v8Str(js.v8Isolate, value);
481 }
482 
483 v8::Local<v8::String> wrap(Lock& js,
484 v8::Local<v8::Context> context,
485 kj::Maybe<v8::Local<v8::Object>> creator,
486 kj::Array<const char> value) {
487 return wrap(js, context, creator, value.asPtr());
488 }
489 
490 template <typename T>
491 requires(kj::isSameType<T, const USVString&>() || kj::isSameType<T, const DOMString&>())
492 v8::Local<v8::String> wrap(
493 Lock& js, v8::Local<v8::Context> context, kj::Maybe<v8::Local<v8::Object>> creator, T value) {
494 // TODO(cleanup): Move to a HeaderStringWrapper in the api directory.
495 return v8Str(js.v8Isolate, value.asPtr());
496 }
497 
498 template <typename T>
499 requires(kj::isSameType<T, kj::String>() || kj::isSameType<T, USVString>() ||
500 kj::isSameType<T, DOMString>())
501 kj::Maybe<T> tryUnwrap(Lock& js,
502 v8::Local<v8::Context> context,
503 v8::Local<v8::Value> handle,
504 T*,
505 kj::Maybe<v8::Local<v8::Object>> parentObject) {
506 // Note that if handle is already a string, calling ToString will just
507 // return handle without any further coercion. For any other type of
508 // value, v8 will try to coerce it into a string. So there is no need
509 // for us to check if handle is a string here or not, ToString does
510 // that for us.
511 JsString str(check(handle->ToString(context)));
512 if constexpr (kj::isSameType<T, kj::String>()) {
513 return str.toString(js);
514 } else if constexpr (kj::isSameType<T, USVString>()) {
515 return str.toUSVString(js);
516 } else if constexpr (kj::isSameType<T, DOMString>()) {
517 return str.toDOMString(js);
518 }
519 
520 KJ_UNREACHABLE;
521 }
522};
523 
524// =======================================================================================
525// Optional (value or undefined) and Maybe (value or null)
526 
527// TypeWrapper mixin for optionals.
528template <typename TypeWrapper>
529class OptionalWrapper {
530 public:
531 template <typename U>
532 static constexpr decltype(auto) getName(Optional<U>*) {
533 return TypeWrapper::getName(static_cast<kj::Decay<U>*>(nullptr));
534 }
535 
536 template <typename U>
537 v8::Local<v8::Value> wrap(Lock& js,
538 v8::Local<v8::Context> context,
539 kj::Maybe<v8::Local<v8::Object>> creator,
540 Optional<U> ptr) {
541 KJ_IF_SOME(p, ptr) {
542 return static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::fwd<U>(p));
543 } else {
544 return js.undefined();
545 }
546 }
547 
548 template <typename U>
549 kj::Maybe<Optional<U>> tryUnwrap(Lock& js,
550 v8::Local<v8::Context> context,
551 v8::Local<v8::Value> handle,
552 Optional<U>*,
553 kj::Maybe<v8::Local<v8::Object>> parentObject) {
554 if (handle->IsUndefined()) {
555 return Optional<U>(kj::none);
556 } else {
557 return static_cast<TypeWrapper*>(this)
558 ->tryUnwrap(js, context, handle, static_cast<kj::Decay<U>*>(nullptr), parentObject)
559 .map([](auto&& value) -> Optional<U> { return kj::fwd<decltype(value)>(value); });
560 }
561 }
562};
563 
564// TypeWrapper mixin for lenient optionals.
565template <typename TypeWrapper>
566class LenientOptionalWrapper {
567 public:
568 template <typename U>
569 static constexpr decltype(auto) getName(LenientOptional<U>*) {
570 return TypeWrapper::getName(static_cast<kj::Decay<U>*>(nullptr));
571 }
572 
573 template <typename U>
574 v8::Local<v8::Value> wrap(Lock& js,
575 v8::Local<v8::Context> context,
576 kj::Maybe<v8::Local<v8::Object>> creator,
577 LenientOptional<U> ptr) {
578 KJ_IF_SOME(p, ptr) {
579 return static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::fwd<U>(p));
580 } else {
581 return js.undefined();
582 }
583 }
584 
585 template <typename U>
586 kj::Maybe<LenientOptional<U>> tryUnwrap(Lock& js,
587 v8::Local<v8::Context> context,
588 v8::Local<v8::Value> handle,
589 LenientOptional<U>*,
590 kj::Maybe<v8::Local<v8::Object>> parentObject) {
591 if (handle->IsUndefined()) {
592 return LenientOptional<U>(kj::none);
593 } else {
594 KJ_IF_SOME(unwrapped,
595 static_cast<TypeWrapper*>(this)->tryUnwrap(
596 js, context, handle, static_cast<kj::Decay<U>*>(nullptr), parentObject)) {
597 return LenientOptional<U>(kj::mv(unwrapped));
598 } else {
599 return LenientOptional<U>(kj::none);
600 }
601 }
602 }
603};
604 
605// TypeWrapper mixin for maybes.
606template <typename TypeWrapper>
607class MaybeWrapper {
608 
609 public:
610 // The constructor here is a bit of a hack. The config is optional and might not be a JsgConfig
611 // object (or convertible to a JsgConfig) if is provided. However, because of the way TypeWrapper
612 // inherits MaybeWrapper, we always end up passing a config option (which might be std::nullptr_t)
613 // The getConfig allows us to handle any case using reasonable defaults.
614 MaybeWrapper(const auto& config): config(getConfig(config)) {}
615 
616 template <typename U>
617 static constexpr decltype(auto) getName(kj::Maybe<U>*) {
618 return TypeWrapper::getName(static_cast<kj::Decay<U>*>(nullptr));
619 }
620 
621 template <typename U>
622 v8::Local<v8::Value> wrap(Lock& js,
623 v8::Local<v8::Context> context,
624 kj::Maybe<v8::Local<v8::Object>> creator,
625 kj::Maybe<U> ptr) {
626 KJ_IF_SOME(p, ptr) {
627 return static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::fwd<U>(p));
628 } else {
629 return js.null();
630 }
631 }
632 
633 template <typename U>
634 kj::Maybe<kj::Maybe<U>> tryUnwrap(Lock& js,
635 v8::Local<v8::Context> context,
636 v8::Local<v8::Value> handle,
637 kj::Maybe<U>*,
638 kj::Maybe<v8::Local<v8::Object>> parentObject) {
639 if (handle->IsNullOrUndefined()) {
640 return kj::Maybe<U>(kj::none);
641 } else if (config.noSubstituteNull) {
642 // There was a bug in the initial version of this method that failed to correctly handle
643 // the following tryUnwrap returning a nullptr because of an incorrect type. The
644 // noSubstituteNull compatibility flag is needed to fix that.
645 return static_cast<TypeWrapper*>(this)
646 ->tryUnwrap(js, context, handle, static_cast<kj::Decay<U>*>(nullptr), parentObject)
647 .map([](auto&& value) -> kj::Maybe<U> { return kj::fwd<decltype(value)>(value); });
648 } else {
649 return static_cast<TypeWrapper*>(this)->tryUnwrap(
650 js, context, handle, static_cast<kj::Decay<U>*>(nullptr), parentObject);
651 }
652 }
653 
654 private:
655 const JsgConfig config;
656};
657 
658// =======================================================================================
659// OneOf / variants
660 
661template <typename T>
662constexpr bool isOneOf = false;
663template <typename... T>
664constexpr bool isOneOf<kj::OneOf<T...>> = true;
665// TODO(cleanup): Move to kj/one-of.h?
666 
667// TypeWrapper mixin for variants.
668template <typename TypeWrapper>
669class OneOfWrapper {
670 public:
671 template <typename... U>
672 static kj::String getName(kj::OneOf<U...>*) {
673 const auto getNameStr = [](auto u) {
674 if constexpr (kj::isSameType<const std::type_info&, decltype(TypeWrapper::getName(u))>()) {
675 return typeName(TypeWrapper::getName(u));
676 } else {
677 return kj::str(TypeWrapper::getName(u));
678 }
679 };
680 
681 return kj::strArray(kj::arr(getNameStr(static_cast<U*>(nullptr))...), " or ");
682 }
683 
684 template <typename U, typename... V>
685 bool wrapHelper(Lock& js,
686 v8::Local<v8::Context> context,
687 kj::Maybe<v8::Local<v8::Object>> creator,
688 kj::OneOf<V...>& in,
689 v8::Local<v8::Value>& out) {
690 if (in.template is<U>()) {
691 out =
692 static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(in.template get<U>()));
693 return true;
694 } else {
695 return false;
696 }
697 }
698 
699 template <typename... U>
700 v8::Local<v8::Value> wrap(Lock& js,
701 v8::Local<v8::Context> context,
702 kj::Maybe<v8::Local<v8::Object>> creator,
703 kj::OneOf<U...> value) {
704 v8::Local<v8::Value> result;
705 if (!(wrapHelper<U>(js, context, creator, value, result) || ...)) {
706 result = js.undefined();
707 }
708 return result;
709 }
710 
711 template <template <typename> class Predicate, typename U, typename... V>
712 bool unwrapHelperRecursive(
713 Lock& js, v8::Local<v8::Context> context, v8::Local<v8::Value> in, kj::OneOf<V...>& out) {
714 if constexpr (isOneOf<U>) {
715 // Ugh, a nested OneOf. We can't just call tryUnwrap(), because then our string/numeric
716 // coercion might trigger early.
717 U val;
718 if (unwrapHelper<Predicate>(js, context, in, val)) {
719 out.template init<U>(kj::mv(val));
720 return true;
721 }
722 } else if constexpr (Predicate<kj::Decay<U>>::value) {
723 KJ_IF_SOME(val,
724 static_cast<TypeWrapper*>(this)->tryUnwrap(
725 js, context, in, static_cast<U*>(nullptr), kj::none)) {
726 out.template init<U>(kj::mv(val));
727 return true;
728 }
729 }
730 return false;
731 }
732 
733 template <template <typename> class Predicate, typename... U>
734 bool unwrapHelper(
735 Lock& js, v8::Local<v8::Context> context, v8::Local<v8::Value> in, kj::OneOf<U...>& out) {
736 return (unwrapHelperRecursive<Predicate, U>(js, context, in, out) || ...);
737 }
738 
739 // Predicates for helping implement nested OneOf unwrapping. These must be struct templates
740 // because we can't pass variable templates as template template parameters.
741 
742 template <typename T>
743 struct IsResourceType {
744 static constexpr bool value = webidl::isNonCallbackInterfaceType<T>;
745 };
746 template <typename T>
747 struct IsFallibleType {
748 static constexpr bool value =
749 !(webidl::isStringType<T> || webidl::isNumericType<T> || webidl::isBooleanType<T>);
750 };
751 template <typename T>
752 struct IsStringType {
753 static constexpr bool value = webidl::isStringType<T>;
754 };
755 template <typename T>
756 struct IsNumericType {
757 static constexpr bool value = webidl::isNumericType<T>;
758 };
759 template <typename T>
760 struct IsBooleanType {
761 static constexpr bool value = webidl::isBooleanType<T>;
762 };
763 
764 template <typename... U>
765 kj::Maybe<kj::OneOf<U...>> tryUnwrap(Lock& js,
766 v8::Local<v8::Context> context,
767 v8::Local<v8::Value> handle,
768 kj::OneOf<U...>*,
769 kj::Maybe<v8::Local<v8::Object>> parentObject) {
770 (void)webidl::UnionTypeValidator<kj::OneOf<U...>>();
771 // Just need to instantiate this, static_asserts will do the rest.
772 
773 // In order for string, numeric, and boolean coercion to function as expected, we need to follow
774 // the algorithm defined by Web IDL section 3.2.22 to convert JS values to OneOfs. That
775 // algorithm is written in a terribly wonky way, of course, but it appears we can restate it
776 // like so:
777 //
778 // - Perform a series of breadth-first-searches on the OneOf, filtering out certain categories
779 // of types on each run. For the types which are not filtered out, perform a tryUnwrap() on
780 // that type, and succeed if that call succeeds (i.e., short-circuit). The filters used for
781 // each pass are the following:
782 // a. Consider only fallible (uncoercible) types.
783 // b. If the JS value is a boolean, consider only boolean types.
784 // c. If the JS value is a number, consider only numeric types.
785 // d. Consider only string types.
786 // e. Consider only numeric types.
787 // f. Consider only boolean types.
788 //
789 // Note the symmetry across steps b-f. This way, strings only get coerced to numbers if the
790 // OneOf doesn't contain a string type, numbers only get coerced to strings if the OneOf doesn't
791 // contain a numeric type, objects only get coerced to a coercible type if there's no matching
792 // object type, null and undefined only get coerced to a coercible type if there's no nullable
793 // type, etc.
794 //
795 // TODO(soon): Hacked this by unwrapping into resource types first, so that we can unwrap
796 // Requests and Responses into Initializers without them being interpreted as dictionaries. I
797 // believe this is actually what the Web IDL spec prescribes anyway, but verify.
798 //
799 // TODO(someday): Prove that this is the same algorithm as the one defined by Web IDL.
800 kj::OneOf<U...> result;
801 if (unwrapHelper<IsResourceType>(js, context, handle, result) ||
802 unwrapHelper<IsFallibleType>(js, context, handle, result) ||
803 (handle->IsBoolean() && unwrapHelper<IsBooleanType>(js, context, handle, result)) ||
804 (handle->IsNumber() && unwrapHelper<IsNumericType>(js, context, handle, result)) ||
805 (handle->IsBigInt() && unwrapHelper<IsNumericType>(js, context, handle, result)) ||
806 (unwrapHelper<IsStringType>(js, context, handle, result)) ||
807 (unwrapHelper<IsNumericType>(js, context, handle, result)) ||
808 (unwrapHelper<IsBooleanType>(js, context, handle, result))) {
809 return kj::mv(result);
810 }
811 return kj::none;
812 }
813};
814 
815// =======================================================================================
816// Arrays
817 
818// TypeWrapper mixin for arrays.
819template <typename TypeWrapper>
820class ArrayWrapper {
821 public:
822 template <typename U>
823 static constexpr const char* getName(kj::Array<U>*) {
824 return "Array";
825 }
826 
827 template <typename U>
828 v8::Local<v8::Value> wrap(Lock& js,
829 v8::Local<v8::Context> context,
830 kj::Maybe<v8::Local<v8::Object>> creator,
831 kj::Array<U> array) {
832 v8::Isolate* isolate = js.v8Isolate;
833 v8::EscapableHandleScope handleScope(isolate);
834 
835 v8::LocalVector<v8::Value> items(isolate, array.size());
836 for (auto n = 0; n < items.size(); n++) {
837 items[n] = static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(array[n]));
838 }
839 auto out = v8::Array::New(isolate, items.data(), items.size());
840 
841 return handleScope.Escape(out);
842 }
843 template <typename U>
844 v8::Local<v8::Value> wrap(Lock& js,
845 v8::Local<v8::Context> context,
846 kj::Maybe<v8::Local<v8::Object>> creator,
847 kj::ArrayPtr<U> array) {
848 v8::Isolate* isolate = js.v8Isolate;
849 v8::EscapableHandleScope handleScope(isolate);
850 
851 v8::LocalVector<v8::Value> items(isolate, array.size());
852 for (auto n = 0; n < items.size(); n++) {
853 items[n] = static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(array[n]));
854 }
855 auto out = v8::Array::New(isolate, items.data(), items.size());
856 
857 return handleScope.Escape(out);
858 }
859 template <typename U>
860 v8::Local<v8::Value> wrap(Lock& js,
861 v8::Local<v8::Context> context,
862 kj::Maybe<v8::Local<v8::Object>> creator,
863 kj::Array<U>& array) {
864 return static_cast<TypeWrapper*>(this)->wrap(js, context, creator, array.asPtr());
865 }
866 
867 template <typename U>
868 kj::Maybe<kj::Array<U>> tryUnwrap(Lock& js,
869 v8::Local<v8::Context> context,
870 v8::Local<v8::Value> handle,
871 kj::Array<U>*,
872 kj::Maybe<v8::Local<v8::Object>> parentObject) {
873 if (!handle->IsArray()) {
874 return kj::none;
875 }
876 
877 auto array = handle.As<v8::Array>();
878 auto length = array->Length();
879 auto builder = kj::heapArrayBuilder<U>(length);
880 for (auto i: kj::zeroTo(length)) {
881 v8::Local<v8::Value> element = check(array->Get(context, i));
882 builder.add(static_cast<TypeWrapper*>(this)->template unwrap<U>(
883 js, context, element, TypeErrorContext::arrayElement(i)));
884 }
885 return builder.finish();
886 }
887};
888 
889// =======================================================================================
890// Sets
891 
892// TypeWrapper mixin for sets.
893template <typename TypeWrapper>
894class SetWrapper {
895 public:
896 template <typename U>
897 static constexpr const char* getName(kj::HashSet<U>*) {
898 return "Set";
899 }
900 
901 template <typename U>
902 v8::Local<v8::Value> wrap(Lock& js,
903 v8::Local<v8::Context> context,
904 kj::Maybe<v8::Local<v8::Object>> creator,
905 kj::HashSet<U> set) {
906 v8::Isolate* isolate = js.v8Isolate;
907 v8::EscapableHandleScope handleScope(isolate);
908 
909 auto out = v8::Set::New(isolate);
910 for (auto& item: set) {
911 v8::HandleScope scope(isolate);
912 check(out->Add(
913 context, static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(item))));
914 }
915 
916 return handleScope.Escape(out);
917 }
918 
919 template <typename U>
920 kj::Maybe<kj::HashSet<U>> tryUnwrap(Lock& js,
921 v8::Local<v8::Context> context,
922 v8::Local<v8::Value> handle,
923 kj::HashSet<U>*,
924 kj::Maybe<v8::Local<v8::Object>> parentObject) {
925 if (!handle->IsSet()) {
926 return kj::none;
927 }
928 
929 auto set = handle.As<v8::Set>();
930 auto array = set->AsArray();
931 auto length = array->Length();
932 auto builder = kj::HashSet<U>();
933 builder.reserve(length);
934 for (auto i: kj::zeroTo(length)) {
935 v8::Local<v8::Value> element = check(array->Get(context, i));
936 auto value = static_cast<TypeWrapper*>(this)->template unwrap<U>(
937 js, context, element, TypeErrorContext::other());
938 builder.upsert(kj::mv(value), [&](U& existing, U&& replacement) {
939 JSG_FAIL_REQUIRE(TypeError, "Duplicate values in the set after unwrapping.");
940 });
941 }
942 return kj::mv(builder);
943 }
944};
945 
946// =======================================================================================
947// ArrayBuffers / ArrayBufferViews
948//
949// This wrapper implements the following wrapping conversions:
950// - kj::Array<kj::byte> -> ArrayBuffer
951//
952// And the following unwrapping conversions:
953// - ArrayBuffer -> kj::Array<kj::byte>
954// (the kj::Array object holds a Global to the unwrapped ArrayBuffer)
955// - ArrayBufferView -> kj::Array<kj::byte>
956// (the kj::Array object holds a Global to the unwrapped ArrayBufferView's backing buffer)
957//
958// Note that there are no conversions for kj::ArrayPtr<kj::byte>, since it does not own its own
959// buffer -- fine in C++, but problematic in a GC language like JS. Restricting the interface to
960// only operate on owned arrays makes memory management simpler and safer in both directions.
961//
962// Logically a kj::Array<byte> could be considered analogous to a Uint8Array in JS, and for a time
963// that was the wrapping conversion implemented by this wrapper. However, the most common use cases
964// in web platform APIs involve accepting BufferSources for processing as immutable input and
965// returning ArrayBuffers. Since a kj::byte does not map to any JavaScript primitive, establishing
966// a mapping between ArrayBuffer/ArrayBufferView and Array<byte> is unambiguous and
967// convenient. The few places where a specific TypedArray is expected (e.g. Uint8Array) can be
968// handled explicitly with a v8::Local<v8::Uint8Array> (or other appropriate TypedArray type).
969//
970// BufferSource arguments to web platform API methods are typically expected to be processed but not
971// mutated, such as the input parameter to TextDecoder.decode(). This processing might happen
972// asynchronously, such as the plaintext parameter to SubtleCrypto.encrypt(). I am unaware of any
973// use of BufferSources which involve mutating the underlying ArrayBuffer -- typically an explicit
974// ArrayBufferView is expected for this case, such as the parameters to crypto.getRandomValues() or
975// the Streams spec's BYOB reader's read() method.
976//
977// This suggests the following rules of thumb:
978//
979// 1. If a BufferSource parameter is used as input to a:
980// - synchronous method: accept a `kj::Array<const kj::byte>`.
981// - asynchronous method (user is allowed to re-use the buffer during processing): accept a
982// `kj::Array<const kj::byte>` and explicitly copy its bytes.
983//
984// 2. If a method accepts an ArrayBufferView that it is expected to mutate:
985// - accept a `v8::Local<v8::ArrayBufferView>` explicitly (handled by V8HandleWrapper in
986// type-wrapper.h) rather than a `kj::Array<kj::byte>` -- otherwise your method's contract
987// will be wider than intended.
988// - use `jsg::asBytes()` as a quick way to get a `kj::ArrayPtr<kj::byte>` view onto it.
989//
990// 3. If a method returns an ArrayBuffer, create and return a `kj::Array<kj::byte>`.
991class ArrayBufferWrapper {
992 public:
993 static constexpr const char* getName(kj::ArrayPtr<byte>*) {
994 return "ArrayBuffer or ArrayBufferView";
995 }
996 static constexpr const char* getName(kj::ArrayPtr<const byte>*) {
997 return "ArrayBuffer or ArrayBufferView";
998 }
999 static constexpr const char* getName(kj::Array<byte>*) {
1000 return "ArrayBuffer or ArrayBufferView";
1001 }
1002 
1003 v8::Local<v8::ArrayBuffer> wrap(jsg::Lock& js,
1004 v8::Local<v8::Context> context,
1005 kj::Maybe<v8::Local<v8::Object>> creator,
1006 kj::Array<byte> value) {
1007 return wrap(js.v8Isolate, creator, kj::mv(value));
1008 }
1009 
1010 v8::Local<v8::ArrayBuffer> wrap(
1011 v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, kj::Array<byte> value) {
1012 // We need to construct a BackingStore that owns the byte array. We use the version of
1013 // v8::ArrayBuffer::NewBackingStore() that accepts a deleter callback, and arrange for it to
1014 // delete an Array<byte> placed on the heap.
1015 //
1016 size_t size = value.size();
1017 if (size == 0) {
1018 // BackingStore doesn't call custom deleter if begin is null, which it often is for empty
1019 // arrays.
1020 return v8::ArrayBuffer::New(isolate, 0);
1021 }
1022 byte* begin = value.begin();
1023 if (isolate->GetGroup().SandboxContains(begin)) {
1024 // TODO(perf): We could avoid an allocation here, perhaps, by decomposing the
1025 // kj::Array<byte> into its component pointer and disposer, and then pass the disposer
1026 // pointer as the "deleter_data" for NewBackingStore. However, KJ doesn't give us any way
1027 // to decompose an Array<T> this way, and it might not want to, as this could make it
1028 // impossible to support unifying Array<T> and Vector<T> in the future (i.e. making all
1029 // Array<T>s growable). So it may be best to stick with allocating an Array<byte> on the
1030 // heap after all...
1031 auto ownerPtr = new kj::Array<byte>(kj::mv(value));
1032 
1033 std::unique_ptr<v8::BackingStore> backing = v8::ArrayBuffer::NewBackingStore(
1034 begin, size, [](void* begin, size_t size, void* ownerPtr) {
1035 delete reinterpret_cast<kj::Array<byte>*>(ownerPtr);
1036 }, ownerPtr);
1037 KJ_REQUIRE(backing != nullptr, "Failed to create ArrayBuffer backing store");
1038 
1039 return v8::ArrayBuffer::New(isolate, kj::mv(backing));
1040 } else {
1041 // The Array is not already inside the sandbox. We have to make a copy and move it in.
1042 // For performance reasons we might want to throw here and fix all callers to allocate
1043 // inside the sandbox.
1044 auto& js = Lock::from(isolate);
1045 auto in_sandbox = js.allocBackingStore(size, Lock::AllocOption::UNINITIALIZED);
1046 
1047 memcpy(in_sandbox->Data(), value.begin(), size);
1048 
1049 return v8::ArrayBuffer::New(isolate, kj::mv(in_sandbox));
1050 }
1051 }
1052 
1053 kj::Maybe<kj::Array<byte>> tryUnwrap(Lock& js,
1054 v8::Local<v8::Context> context,
1055 v8::Local<v8::Value> handle,
1056 kj::Array<byte>*,
1057 kj::Maybe<v8::Local<v8::Object>> parentObject) {
1058 if (handle->IsArrayBufferView()) {
1059 return asBytes(handle.As<v8::ArrayBufferView>());
1060 } else if (handle->IsArrayBuffer()) {
1061 return asBytes(handle.As<v8::ArrayBuffer>());
1062 } else if (handle->IsSharedArrayBuffer()) {
1063 return asBytes(handle.As<v8::SharedArrayBuffer>());
1064 }
1065 return kj::none;
1066 }
1067 
1068 kj::Maybe<kj::Array<const byte>> tryUnwrap(Lock& js,
1069 v8::Local<v8::Context> context,
1070 v8::Local<v8::Value> handle,
1071 kj::Array<const byte>*,
1072 kj::Maybe<v8::Local<v8::Object>> parentObject) {
1073 return tryUnwrap(js, context, handle, static_cast<kj::Array<byte>*>(nullptr), parentObject);
1074 }
1075};
1076 
1077// =======================================================================================
1078// Dicts
1079 
1080// TypeWrapper mixin for dictionaries (objects used as string -> value maps).
1081template <typename TypeWrapper>
1082class DictWrapper {
1083 public:
1084 template <typename K, typename V>
1085 static constexpr const char* getName(Dict<V, K>*) {
1086 return "object";
1087 }
1088 
1089 template <typename K, typename V>
1090 v8::Local<v8::Value> wrap(Lock& js,
1091 v8::Local<v8::Context> context,
1092 kj::Maybe<v8::Local<v8::Object>> creator,
1093 Dict<V, K> dict) {
1094 static_assert(webidl::isStringType<K>, "Dicts must be keyed on a string type.");
1095 
1096 v8::Isolate* isolate = js.v8Isolate;
1097 v8::EscapableHandleScope handleScope(isolate);
1098 auto out = v8::Object::New(isolate);
1099 for (auto& field: dict.fields) {
1100 // Set() returns Maybe<bool>. As usual, if the Maybe is null, then there was an exception,
1101 // but I have no idea what it means if the Maybe was filled in with the boolean value false...
1102 KJ_ASSERT(check(out->Set(context,
1103 static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(field.name)),
1104 static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(field.value)))));
1105 }
1106 return handleScope.Escape(out);
1107 }
1108 
1109 template <typename K, typename V>
1110 kj::Maybe<Dict<V, K>> tryUnwrap(Lock& js,
1111 v8::Local<v8::Context> context,
1112 v8::Local<v8::Value> handle,
1113 Dict<V, K>*,
1114 kj::Maybe<v8::Local<v8::Object>> parentObject) {
1115 static_assert(webidl::isStringType<K>, "Dicts must be keyed on a string type.");
1116 
1117 auto& wrapper = static_cast<TypeWrapper&>(*this);
1118 
1119 if (!handle->IsObject() || handle->IsArray()) {
1120 return kj::none;
1121 }
1122 
1123 auto object = handle.As<v8::Object>();
1124 v8::Local<v8::Array> names = check(object->GetOwnPropertyNames(context));
1125 auto length = names->Length();
1126 auto builder = kj::heapArrayBuilder<typename Dict<V, K>::Field>(length);
1127 for (auto i: kj::zeroTo(length)) {
1128 v8::Local<v8::String> name = check(check(names->Get(context, i))->ToString(context));
1129 v8::Local<v8::Value> value = check(object->Get(context, name));
1130 
1131 if constexpr (kj::isSameType<K, kj::String>()) {
1132 auto strName = JsString(name).toString(js);
1133 const char* cstrName = strName.cStr();
1134 builder.add(typename Dict<V, K>::Field{kj::mv(strName),
1135 wrapper.template unwrap<V>(
1136 js, context, value, TypeErrorContext::dictField(cstrName), object)});
1137 } else {
1138 // Here we have to be a bit more careful than for the kj::String case. The unwrap<K>() call
1139 // may throw, but we need the name in UTF-8 for the very exception that it needs to throw.
1140 // Thus, we do the unwrapping manually and UTF-8-convert the name only if it's needed.
1141 auto unwrappedName = wrapper.tryUnwrap(js, context, name, static_cast<K*>(nullptr), object);
1142 if (unwrappedName == kj::none) {
1143 auto strName = JsString(name).toString(js);
1144 throwTypeError(js.v8Isolate, TypeErrorContext::dictKey(strName.cStr()),
1145 TypeWrapper::getName(static_cast<K*>(nullptr)));
1146 }
1147 auto unwrappedValue =
1148 wrapper.tryUnwrap(js, context, value, static_cast<V*>(nullptr), object);
1149 if (unwrappedValue == kj::none) {
1150 auto strName = JsString(name).toString(js);
1151 throwTypeError(js.v8Isolate, TypeErrorContext::dictField(strName.cStr()),
1152 TypeWrapper::getName(static_cast<V*>(nullptr)));
1153 }
1154 builder.add(typename Dict<V, K>::Field{
1155 KJ_ASSERT_NONNULL(kj::mv(unwrappedName)), KJ_ASSERT_NONNULL(kj::mv(unwrappedValue))});
1156 }
1157 }
1158 return Dict<V, K>{builder.finish()};
1159 }
1160};
1161 
1162// =======================================================================================
1163// Dates
1164 
1165class DateWrapper {
1166 public:
1167 static constexpr const char* getName(kj::Date*) {
1168 return "date";
1169 }
1170 
1171 v8::Local<v8::Value> wrap(Lock& js,
1172 v8::Local<v8::Context> context,
1173 kj::Maybe<v8::Local<v8::Object>> creator,
1174 kj::Date date) {
1175 return check(v8::Date::New(context, (date - kj::UNIX_EPOCH) / kj::MILLISECONDS));
1176 }
1177 
1178 kj::Maybe<kj::Date> tryUnwrap(Lock& js,
1179 v8::Local<v8::Context> context,
1180 v8::Local<v8::Value> handle,
1181 kj::Date*,
1182 kj::Maybe<v8::Local<v8::Object>> parentObject) {
1183 if (handle->IsDate()) {
1184 double millis = handle.template As<v8::Date>()->ValueOf();
1185 return toKjDate(millis);
1186 } else if (handle->IsNumber()) {
1187 double millis = handle.template As<v8::Number>()->Value();
1188 return toKjDate(millis);
1189 } else {
1190 return kj::none;
1191 }
1192 }
1193 
1194 private:
1195 kj::Date toKjDate(double millis) {
1196 JSG_REQUIRE(isFinite(millis), TypeError,
1197 "The value cannot be converted because it is not a valid Date.");
1198 
1199 // JS Date uses milliseconds stored as a double-precision float to represent times
1200 // KJ uses nanoseconds stored as an int64_t, which is significantly smaller but larger
1201 // than my lifetime.
1202 //
1203 // For most use-cases, throwing when we encounter a date outside of KJ's supported range is OK.
1204 // API's that need to support time-travelers or historians may need to consider using the
1205 // V8 Date type directly.
1206 constexpr double millisToNanos = kj::MILLISECONDS / kj::NANOSECONDS;
1207 double nanos = millis * millisToNanos;
1208 JSG_REQUIRE(nanos < static_cast<int64_t>(kj::maxValue), TypeError,
1209 "This API doesn't support dates after 2189.");
1210 JSG_REQUIRE(nanos > static_cast<int64_t>(kj::minValue), TypeError,
1211 "This API doesn't support dates before 1687.");
1212 return kj::UNIX_EPOCH + static_cast<int64_t>(millis) * kj::MILLISECONDS;
1213 };
1214};
1215 
1216// =======================================================================================
1217// NonCoercible<T>
1218 
1219template <typename TypeWrapper>
1220class NonCoercibleWrapper {
1221 public:
1222 template <CoercibleType T>
1223 static auto getName(NonCoercible<T>*) {
1224 return TypeWrapper::getName(static_cast<T*>(nullptr));
1225 }
1226 
1227 template <CoercibleType T>
1228 v8::Local<v8::Value> wrap(Lock& js,
1229 v8::Local<v8::Context> context,
1230 kj::Maybe<v8::Local<v8::Object>> creator,
1231 NonCoercible<T>) = delete;
1232 
1233 template <CoercibleType T>
1234 kj::Maybe<NonCoercible<T>> tryUnwrap(Lock& js,
1235 v8::Local<v8::Context> context,
1236 v8::Local<v8::Value> handle,
1237 NonCoercible<T>*,
1238 kj::Maybe<v8::Local<v8::Object>> parentObject) {
1239 auto& wrapper = static_cast<TypeWrapper&>(*this);
1240 if constexpr (kj::isSameType<kj::String, T>() || kj::isSameType<jsg::USVString, T>() ||
1241 kj::isSameType<jsg::DOMString, T>()) {
1242 if (!handle->IsString()) return kj::none;
1243 KJ_IF_SOME(value,
1244 wrapper.tryUnwrap(js, context, handle, static_cast<T*>(nullptr), parentObject)) {
1245 return NonCoercible<T>{
1246 .value = kj::mv(value),
1247 };
1248 }
1249 return kj::none;
1250 } else if constexpr (kj::isSameType<bool, T>()) {
1251 if (!handle->IsBoolean()) return kj::none;
1252 return wrapper.tryUnwrap(js, context, handle, static_cast<T*>(nullptr), parentObject)
1253 .map([](auto& value) {
1254 return NonCoercible<T>{
1255 .value = value,
1256 };
1257 });
1258 } else if constexpr (kj::isSameType<double, T>()) {
1259 if (!handle->IsNumber()) return kj::none;
1260 return wrapper.tryUnwrap(js, context, handle, static_cast<T*>(nullptr), parentObject)
1261 .map([](auto& value) {
1262 return NonCoercible<T>{
1263 .value = value,
1264 };
1265 });
1266 } else {
1267 return nullptr;
1268 }
1269 }
1270};
1271 
1272// =======================================================================================
1273// MemoizedIdentity<T>
1274 
1275template <typename T>
1276void MemoizedIdentity<T>::visitForGc(GcVisitor& visitor) {
1277 KJ_SWITCH_ONEOF(value) {
1278 KJ_CASE_ONEOF(raw, T) {
1279 if constexpr (isGcVisitable<T>()) {
1280 visitor.visit(raw);
1281 }
1282 }
1283 KJ_CASE_ONEOF(handle, Value) {
1284 return visitor.visit(handle);
1285 }
1286 }
1287}
1288 
1289template <typename TypeWrapper>
1290class MemoizedIdentityWrapper {
1291 public:
1292 template <typename T>
1293 static auto getName(MemoizedIdentity<T>*) {
1294 return TypeWrapper::getName(static_cast<T*>(nullptr));
1295 }
1296 
1297 template <typename T>
1298 v8::Local<v8::Value> wrap(Lock& js,
1299 v8::Local<v8::Context> context,
1300 kj::Maybe<v8::Local<v8::Object>> creator,
1301 MemoizedIdentity<T>& value) {
1302 auto& wrapper = static_cast<TypeWrapper&>(*this);
1303 KJ_SWITCH_ONEOF(value.value) {
1304 KJ_CASE_ONEOF(raw, T) {
1305 auto handle = wrapper.wrap(js, context, creator, kj::mv(raw));
1306 value.value.template init<Value>(js.v8Isolate, handle);
1307 return handle;
1308 }
1309 KJ_CASE_ONEOF(handle, Value) {
1310 return handle.getHandle(js.v8Isolate);
1311 }
1312 }
1313 KJ_UNREACHABLE;
1314 }
1315 
1316 template <typename T>
1317 kj::Maybe<MemoizedIdentity<T>> tryUnwrap(Lock& js,
1318 v8::Local<v8::Context> context,
1319 v8::Local<v8::Value> handle,
1320 MemoizedIdentity<T>*,
1321 kj::Maybe<v8::Local<v8::Object>> parentObject) = delete;
1322};
1323 
1324// =======================================================================================
1325// Identified<T>
1326 
1327template <typename TypeWrapper>
1328class IdentifiedWrapper {
1329 public:
1330 template <typename T>
1331 static auto getName(Identified<T>*) {
1332 return TypeWrapper::getName(static_cast<T*>(nullptr));
1333 }
1334 
1335 template <typename T>
1336 v8::Local<v8::Value> wrap(Lock& js,
1337 v8::Local<v8::Context> context,
1338 kj::Maybe<v8::Local<v8::Object>> creator,
1339 Identified<T>& value) = delete;
1340 
1341 template <typename T>
1342 kj::Maybe<Identified<T>> tryUnwrap(Lock& js,
1343 v8::Local<v8::Context> context,
1344 v8::Local<v8::Value> handle,
1345 Identified<T>*,
1346 kj::Maybe<v8::Local<v8::Object>> parentObject) {
1347 if (!handle->IsObject()) {
1348 return kj::none;
1349 }
1350 
1351 auto& wrapper = static_cast<TypeWrapper&>(*this);
1352 return wrapper.tryUnwrap(js, context, handle, static_cast<T*>(nullptr), parentObject)
1353 .map([&](T&& value) -> Identified<T> {
1354 auto isolate = js.v8Isolate;
1355 auto obj = handle.As<v8::Object>();
1356 return {.identity = {isolate, obj}, .unwrapped = kj::mv(value)};
1357 });
1358 }
1359};
1360 
1361// =======================================================================================
1362// SelfRef
1363 
1364class SelfRefWrapper {
1365 public:
1366 static auto getName(SelfRef*) {
1367 return "SelfRef";
1368 }
1369 
1370 v8::Local<v8::Value> wrap(Lock& js,
1371 v8::Local<v8::Context> context,
1372 kj::Maybe<v8::Local<v8::Object>> creator,
1373 const SelfRef& value) = delete;
1374 
1375 kj::Maybe<SelfRef> tryUnwrap(Lock& js,
1376 v8::Local<v8::Context> context,
1377 v8::Local<v8::Value> handle,
1378 SelfRef*,
1379 kj::Maybe<v8::Local<v8::Object>> parentObject) {
1380 // I'm sticking this here because it's related and I'm lazy.
1381 return SelfRef(js.v8Isolate,
1382 KJ_ASSERT_NONNULL(parentObject, "SelfRef can only be used as a member of a JSG_STRUCT."));
1383 }
1384};
1385 
1386// =======================================================================================
1387// kj::Exception
1388//
1389// The kj::Exception wrapper handles the translation of so-called "tunneled" exceptions
1390// between KJ and JavaScript. The wrapper is capable of turning any JavaScript value into
1391// a kj::Exception with the caveat that the kj::Exception is not guaranteed to retain all
1392// of the detail. Likewise, it can turn a kj::Exception with the correct metadata into a
1393// reasonable JavaScript exception.
1394 
1395class DOMException;
1396 
1397template <typename TypeWrapper>
1398class ExceptionWrapper {
1399 public:
1400 static constexpr const char* getName(kj::Exception*) {
1401 return "Exception";
1402 }
1403 
1404 v8::Local<v8::Value> wrap(Lock& js,
1405 v8::Local<v8::Context> context,
1406 kj::Maybe<v8::Local<v8::Object>> creator,
1407 kj::Exception exception) {
1408 return js.exceptionToJsValue(kj::mv(exception)).getHandle(js);
1409 }
1410 
1411 kj::Maybe<kj::Exception> tryUnwrap(Lock& js,
1412 v8::Local<v8::Context> context,
1413 v8::Local<v8::Value> handle,
1414 kj::Exception*,
1415 kj::Maybe<v8::Local<v8::Object>> parentObject) {
1416 
1417 // If handle is a DOMException, then createTunneledException will not work
1418 // here. We have to manually handle the DOMException case.
1419 //
1420 // Note that this is a general issue with any JSG_RESOURCE_TYPE that we
1421 // happen to use as Errors. The createTunneledException() method uses V8's
1422 // ToDetailString() to extract the detail about the error in a manner that
1423 // is safe and side-effect free. Unfortunately, that mechanism does not
1424 // work for JSG_RESOURCE_TYPE objects that are used as errors. For those,
1425 // we need to drop down to the C++ interface and generate the kj::Exception
1426 // ourselves. If any additional JSG_RESOURCE_TYPE error-like things are
1427 // introduced, they'll need to be handled explicitly here also.
1428 auto& wrapper = TypeWrapper::from(js.v8Isolate);
1429 kj::Exception result = [&]() {
1430 kj::Exception::Type excType = [&]() {
1431 // Use .retryable and .overloaded properties as hints for what kj exception type to use.
1432 if (handle->IsObject()) {
1433 auto object = handle.As<v8::Object>();
1434 
1435 if (js.toBool(check(object->Get(context, v8StrIntern(js.v8Isolate, "overloaded"_kj))))) {
1436 return kj::Exception::Type::OVERLOADED;
1437 }
1438 if (js.toBool(check(object->Get(context, v8StrIntern(js.v8Isolate, "retryable"_kj))))) {
1439 return kj::Exception::Type::DISCONNECTED;
1440 }
1441 }
1442 return kj::Exception::Type::FAILED;
1443 }();
1444 
1445 KJ_IF_SOME(domException,
1446 wrapper.tryUnwrap(
1447 js, context, handle, static_cast<DOMException*>(nullptr), parentObject)) {
1448 return KJ_EXCEPTION(FAILED,
1449 kj::str("jsg.DOMException(", domException.getName(), "): ", domException.getMessage()));
1450 } else {
1451 
1452 static const constexpr kj::StringPtr PREFIXES[] = {
1453#define V(name, _) name##_kj,
1454 JS_ERROR_TYPES(V)
1455#undef V
1456 "DOMException"_kj,
1457 };
1458 
1459 kj::String reason;
1460 if (!handle->IsObject()) {
1461 // if the argument isn't an object, it couldn't possibly be an Error.
1462 reason = kj::str(JSG_EXCEPTION(Error) ": ", handle);
1463 } else {
1464 reason = kj::str(handle);
1465 bool found = false;
1466 // If the error message starts with a platform error type that we tunnel,
1467 // prefix it with "jsg."
1468 for (auto name: PREFIXES) {
1469 if (reason.startsWith(name)) {
1470 reason = kj::str("jsg.", reason);
1471 found = true;
1472 break;
1473 }
1474 }
1475 // Everything else should just come through as a normal error.
1476 if (!found) {
1477 reason = kj::str(JSG_EXCEPTION(Error) ": ", reason);
1478 }
1479 }
1480 return kj::Exception(excType, __FILE__, __LINE__, kj::mv(reason));
1481 }
1482 }();
1483 
1484 addExceptionDetail(js, result, handle);
1485 addJsExceptionMetadata(js, result, handle);
1486 return result;
1487 }
1488};
1489 
1490} // namespace workerd::jsg