From fc2590a7e33db12eda78b45a6ca27ac6adfaec9b Mon Sep 17 00:00:00 2001 From: AdityaAtulTewari Date: Fri, 16 Aug 2024 15:42:37 -0500 Subject: [PATCH] JSG_STRUCT_TS_OVERRIDE_DYNAMIC by refactoring JSG_STRUCT --- src/workerd/jsg/jsg.h | 87 ++++++++++++++++++++++++++++++++-------- src/workerd/jsg/struct.h | 13 +++++- src/workerd/jsg/util.h | 3 ++ 3 files changed, 84 insertions(+), 19 deletions(-) diff --git a/src/workerd/jsg/jsg.h b/src/workerd/jsg/jsg.h index 96c7ba0bf635..1d32fe7e3c18 100644 --- a/src/workerd/jsg/jsg.h +++ b/src/workerd/jsg/jsg.h @@ -583,6 +583,13 @@ using HasGetTemplateOverload = decltype( #define JSG_STRUCT_TS_OVERRIDE(...) \ static constexpr char _JSG_STRUCT_TS_OVERRIDE_DO_NOT_USE_DIRECTLY[] = JSG_STRING_LITERAL(__VA_ARGS__) +// Like JSG_STRUCT_TS_OVERRIDE, however it enables dynamic selection of TS_OVERRIDE. +// Should be placed adjacent to the JSG_STRUCT delcaration, inside the same struct definition. +#define JSG_STRUCT_TS_OVERRIDE_DYNAMIC(...) \ + static void jsgConfiguration(__VA_ARGS__); \ + template \ + static void registerTypeScriptDynamicOverride(Registry& registry, ##__VA_ARGS__) + // Like JSG_TS_DEFINE but for use with JSG_STRUCT. Should be placed adjacent to the JSG_STRUCT // declaration, inside the same `struct` definition. See the `## TypeScript`section of the JSG README.md // for more details. @@ -623,6 +630,41 @@ namespace { struct HasStructTypeScriptDefine : std::true_type { }; } +// This function is the first part of a JSG_STRUCT and defines the Kind, fields, and type wrapper +#define _JSG_STRUCT_INTERNAL_KIND_AND_FIELDS_AND_TYPE_WRAPPER(...) \ + static constexpr ::workerd::jsg::JsgKind JSG_KIND KJ_UNUSED = \ + ::workerd::jsg::JsgKind::STRUCT; \ + static constexpr char JSG_FOR_EACH(JSG_STRUCT_FIELD_NAME, , __VA_ARGS__); \ + template \ + using JsgFieldWrappers = ::workerd::jsg::TypeTuple< \ + JSG_FOR_EACH(JSG_STRUCT_FIELD, , __VA_ARGS__) \ + > + +// This registers TS ROOT objects within a JSG_STRUCT +#define _JSG_STRUCT_REGISTER_MEMBERS_TS_ROOT() \ + if constexpr (::workerd::jsg::HasStructTypeScriptRoot::value) { \ + registry.registerTypeScriptRoot(); \ + } + +// This registers TS OVERRIDE objects within a JSG_STRUCT +#define _JSG_STRUCT_REGISTER_MEMBERS_TS_OVERRIDE() \ + if constexpr (::workerd::jsg::HasStructTypeScriptOverride::value) { \ + registry.template registerTypeScriptOverride(); \ + } + +// This registers TS OVERRIDE for dynamic objects +#define _JSG_STRUCT_REGISTER_MEMBERS_TS_DYNAMIC_OVERRIDE() \ + if constexpr (requires (jsg::GetConfiguration arg) \ + { registerTypeScriptDynamicOverride(registry, arg); }) { \ + registerTypeScriptDynamicOverride(registry, arg); \ + } + +// This registers TS DEFINE objects within a register members function +#define _JSG_STRUCT_REGISTER_MEMBERS_TS_DEFINE() \ + if constexpr (::workerd::jsg::HasStructTypeScriptDefine::value) { \ + registry.template registerTypeScriptDefine(); \ + } + // Nest this inside a simple struct declaration in order to support translating it to/from a // JavaScript object / Web IDL dictionary. // @@ -641,6 +683,20 @@ namespace { // all in JavaScript when the optional is null in C++ (as opposed to the field being present but // assigned the value `undefined`). // +// Note that if a `validate` function is provided, then it will be called after the struct is +// unwrapped from v8. This would be an appropriate time to throw an error. +// Signature: void validate(jsg::Lock& js); +// Example: +// struct ValidatingFoo { +// kj::String abc; +// void validate(jsg::Lock& js) { +// JSG_REQUIRE(abc.size() != 0, TypeError, "Field 'abc' had no length in 'ValidatingFoo'."); +// } +// JSG_STRUCT(abc); +// }; +// +// In this example the validate method would throw a `TypeError` if the size of the `abc` field was zero. +// // Fields with a starting '$' will have that dollar sign prefix stripped in the JS binding. A // motivating example to enact that change was WebCrypto which has a field in a dictionary called // "public". '$' was chosen as a token we can use because it's a character for a C++ identifier. If @@ -649,26 +705,23 @@ namespace { // exporting), then you should be able to use '$$' as the identifier prefix in C++ since only the // first '$' gets stripped. #define JSG_STRUCT(...) \ - static constexpr ::workerd::jsg::JsgKind JSG_KIND KJ_UNUSED = \ - ::workerd::jsg::JsgKind::STRUCT; \ - static constexpr char JSG_FOR_EACH(JSG_STRUCT_FIELD_NAME, , __VA_ARGS__); \ + _JSG_STRUCT_INTERNAL_KIND_AND_FIELDS_AND_TYPE_WRAPPER(__VA_ARGS__); \ template \ - static void registerMembers(Registry& registry) { \ + static void registerMembers(Registry& registry) \ + requires (!jsg::HasConfiguration) { \ JSG_FOR_EACH(JSG_STRUCT_REGISTER_MEMBER, , __VA_ARGS__); \ - if constexpr (::workerd::jsg::HasStructTypeScriptRoot::value) { \ - registry.registerTypeScriptRoot(); \ - } \ - if constexpr (::workerd::jsg::HasStructTypeScriptOverride::value) { \ - registry.template registerTypeScriptOverride(); \ - } \ - if constexpr (::workerd::jsg::HasStructTypeScriptDefine::value) { \ - registry.template registerTypeScriptDefine(); \ - } \ + _JSG_STRUCT_REGISTER_MEMBERS_TS_ROOT(); \ + _JSG_STRUCT_REGISTER_MEMBERS_TS_OVERRIDE(); \ + _JSG_STRUCT_REGISTER_MEMBERS_TS_DEFINE(); \ } \ - template \ - using JsgFieldWrappers = ::workerd::jsg::TypeTuple< \ - JSG_FOR_EACH(JSG_STRUCT_FIELD, , __VA_ARGS__) \ - > + template \ + static void registerMembers(Registry& registry, jsg::GetConfiguration arg) \ + requires jsg::HasConfiguration { \ + JSG_FOR_EACH(JSG_STRUCT_REGISTER_MEMBER, , __VA_ARGS__); \ + _JSG_STRUCT_REGISTER_MEMBERS_TS_ROOT(); \ + _JSG_STRUCT_REGISTER_MEMBERS_TS_DYNAMIC_OVERRIDE(); \ + _JSG_STRUCT_REGISTER_MEMBERS_TS_DEFINE(); \ + } namespace { template diff --git a/src/workerd/jsg/struct.h b/src/workerd/jsg/struct.h index 31482bb470b4..6030e1cd48ff 100644 --- a/src/workerd/jsg/struct.h +++ b/src/workerd/jsg/struct.h @@ -118,10 +118,19 @@ class StructWrapper, kj::_::Indexes(fields).unwrap(static_cast(*this), isolate, context, in)... }; + + // Note that if a `validate` function is provided, then it will be called after the struct is + // unwrapped from v8. This would be an appropriate time to throw an error. + // Signature: void validate(jsg::Lock& js); + if constexpr (requires (jsg::Lock& js) { t.validate(js); }) { + jsg::Lock& js = jsg::Lock::from(isolate); + t.validate(js); + } + + return t; } void newContext() = delete; diff --git a/src/workerd/jsg/util.h b/src/workerd/jsg/util.h index 1caa95a471ec..129cc29e9956 100644 --- a/src/workerd/jsg/util.h +++ b/src/workerd/jsg/util.h @@ -411,6 +411,9 @@ template auto getParameterType(void (*)(Arg)) -> Arg; // SFINAE-friendly accessor for a resource type's configuration parameter. template using GetConfiguration = decltype(getParameterType(&T::jsgConfiguration)); +template concept HasConfiguration = + requires (GetConfiguration arg) { T::jsgConfiguration(arg); }; + inline bool isFinite(double value) { return !(kj::isNaN(value) || value == kj::inf() || value == -kj::inf()); }