Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Workflows to ValidationErrorReporter #3509

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 30 additions & 10 deletions src/workerd/io/worker.c++
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ struct Worker::Impl {
kj::HashMap<kj::String, api::ExportedHandler> namedHandlers;
kj::HashMap<kj::String, ActorClassInfo> actorClasses;
kj::HashMap<kj::String, EntrypointClass> statelessClasses;
kj::HashMap<kj::String, EntrypointClass> workflowClasses;

// If set, then any attempt to use this worker shall throw this exception.
kj::Maybe<kj::Exception> permanentException;
Expand Down Expand Up @@ -1691,7 +1692,7 @@ Worker::Worker(kj::Own<const Script> scriptParam,
impl->statelessClasses.insert(kj::mv(handler.name), kj::mv(cls));
return;
} else if (handle == entrypointClasses.workflowEntrypoint) {
impl->statelessClasses.insert(kj::mv(handler.name), kj::mv(cls));
impl->workflowClasses.insert(kj::mv(handler.name), kj::mv(cls));
return;
}

Expand Down Expand Up @@ -2209,20 +2210,17 @@ void Worker::Lock::validateHandlers(ValidationErrorReporter& errorReporter) {
"did not produce a startup-time error.");
}
}
for (auto& entry: worker.impl->statelessClasses) {
// We want to report all of the stateless class's members. To do this, we examine its
// prototype, and its prototype's prototype, and so on, until we get to Object's
// prototype, which we ignore.
auto entrypointName = getEntrypointName(entry.key);

auto getHandlersForClass = [&](EntrypointClass& entrypointClass) -> kj::Array<kj::String> {
kj::HashSet<kj::String> seenNames;
js.withinHandleScope([&]() {
// Find the prototype for `Object` by creating one.
auto obj = js.obj();
jsg::JsValue prototypeOfObject = obj.getPrototype(js);

// Walk the prototype chain.
jsg::JsObject ctor(KJ_ASSERT_NONNULL(entry.value.tryGetHandle(js.v8Isolate)));
jsg::JsObject ctor(KJ_ASSERT_NONNULL(entrypointClass.tryGetHandle(js.v8Isolate)));
jsg::JsValue proto = ctor.get(js, "prototype");
kj::HashSet<kj::String> seenNames;
for (;;) {
auto protoObj = JSG_REQUIRE_NONNULL(proto.tryCast<jsg::JsObject>(), TypeError,
"Exported entrypoint class's prototype chain does not end in Object.");
Expand All @@ -2249,9 +2247,31 @@ void Worker::Lock::validateHandlers(ValidationErrorReporter& errorReporter) {

proto = protoObj.getPrototype(js);
}

errorReporter.addEntrypoint(entrypointName, KJ_MAP(n, seenNames) { return kj::mv(n); });
});
return KJ_MAP(n, seenNames) { return kj::mv(n); };
};

for (auto& entry: worker.impl->workflowClasses) {
KJ_IF_SOME(entrypointName, getEntrypointName(entry.key)) {
// We also want to check for handlers in workflows - we primarily want to see if the provided worker
// has exposed the `run` handler inside of the class.
auto methods = getHandlersForClass(entry.value);
errorReporter.addWorkflowClass(entrypointName, kj::mv(methods));
} else {
// Similiar to Durable Objects, Workflow cannot be the default entrypoint (at the time of writing).
LOG_PERIODICALLY(ERROR,
"Exported Workflow class cannot be the default entrypoint. This doesn't work, but historically "
"did not produce a startup-time error.");
}
}

for (auto& entry: worker.impl->statelessClasses) {
// We want to report all of the stateless class's members. To do this, we examine its
// prototype, and its prototype's prototype, and so on, until we get to Object's
// prototype, which we ignore.
auto entrypointName = getEntrypointName(entry.key);
auto methods = getHandlersForClass(entry.value);
errorReporter.addEntrypoint(entrypointName, kj::mv(methods));
}
}
});
Expand Down
7 changes: 7 additions & 0 deletions src/workerd/io/worker.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ class Worker: public kj::AtomicRefcounted {

// Report that the Worker exports a Durable Object class with the given name.
virtual void addActorClass(kj::StringPtr exportName) = 0;

// Report that the Worker exports a Workflow class with the given name.
virtual void addWorkflowClass(kj::StringPtr exportName, kj::Array<kj::String> methods) = 0;
};

class LockType;
Expand Down Expand Up @@ -905,6 +908,10 @@ struct SimpleWorkerErrorReporter final: public Worker::ValidationErrorReporter {
KJ_UNREACHABLE;
}

void addWorkflowClass(kj::StringPtr exportName, kj::Array<kj::String> methods) override {
KJ_UNREACHABLE;
}

SimpleWorkerErrorReporter() = default;
KJ_DISALLOW_COPY_AND_MOVE(SimpleWorkerErrorReporter);
kj::Vector<kj::String> errors;
Expand Down
4 changes: 4 additions & 0 deletions src/workerd/server/server.c++
Original file line number Diff line number Diff line change
Expand Up @@ -3164,6 +3164,10 @@ kj::Own<Server::Service> Server::makeWorker(kj::StringPtr name,
void addActorClass(kj::StringPtr exportName) override {
actorClasses.insert(kj::str(exportName));
}

void addWorkflowClass(kj::StringPtr exportName, kj::Array<kj::String> methods) override {
// This is only used for validation and has no runtime implications, at least for now.
}
};

ErrorReporter errorReporter(*this, name);
Expand Down
1 change: 1 addition & 0 deletions src/workerd/tests/test-fixture.c++
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ struct MockErrorReporter final: public Worker::ValidationErrorReporter {

void addEntrypoint(kj::Maybe<kj::StringPtr> exportName, kj::Array<kj::String> methods) override {}
void addActorClass(kj::StringPtr exportName) override {}
void addWorkflowClass(kj::StringPtr exportName, kj::Array<kj::String> methods) override {}
};

inline server::config::Worker::Reader buildConfig(
Expand Down
Loading