Skip to content

Waiter codegen plugin + waiters#3752

Open
sbaluja wants to merge 8 commits intomainfrom
waiter-codegen
Open

Waiter codegen plugin + waiters#3752
sbaluja wants to merge 8 commits intomainfrom
waiter-codegen

Conversation

@sbaluja
Copy link
Contributor

@sbaluja sbaluja commented Mar 17, 2026

Issue #, if available:

Description of changes:

  • Waiter codegen plugin
  • Clients with updated CRTP waiter inheritance
  • Cloudwatch paginator support (was missing the inheritance for CBOR codegen)

Check all that applies:

  • Did a review by yourself.
  • Added proper tests to cover this PR. (If tests are not applicable, explain.)
  • Checked if this PR is a breaking (APIs have been changed) change.
  • Checked if this PR will not introduce cross-platform inconsistent behavior.
  • Checked if this PR would require a ReadMe/Wiki update.

Check which platforms you have built SDK on to verify the correctness of this PR.

  • Linux
  • Windows
  • Android
  • MacOS
  • IOS
  • Other Platforms

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Copy link
Contributor

@kai-ion kai-ion Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a EC2WaiterCompilationTests.cpp similar to EC2PaginationCompilationTests.cpp that is code generated in smithy to catch any code that doesnt compile or with missing header files.

@@ -76,10 +80,14 @@ tasks.register("generate-smithy-build") {
if (filteredServiceList.isEmpty() || c2jName in filteredServiceList) {
val mockProjectionContents = Node.objectNodeBuilder()
.withMember("plugins", Node.objectNode()
.withMember("smithy-cpp-codegen", Node.objectNodeBuilder()
.withMember("smithy-cpp-codegen-paginators", Node.objectNodeBuilder()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we're repeating this projection a lot and might need to add more if we move more c2j stuff to smithy, we could add a helper funtion

fun pluginConfig(c2jMap: String, namespaceMappings: String): ObjectNode {
    return Node.objectNode()
        .withMember("smithy-cpp-codegen-paginators", Node.objectNodeBuilder()
            .withMember("c2jMap", Node.from(c2jMap))
            .withMember("namespaceMappings", Node.from(namespaceMappings))
            .build())
        .withMember("smithy-cpp-codegen-waiters", Node.objectNodeBuilder()
            .withMember("c2jMap", Node.from(c2jMap))
            .withMember("namespaceMappings", Node.from(namespaceMappings))
            .build())
}

that just calls .withMember("plugins", pluginConfig(c2jMapStr, namespaceMappings)).


template <typename DerivedClient = EC2Client>
class EC2Waiter {
public:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to add user agent metrics similar to PR

Aws::Utils::WaiterOutcome<Model::DescribeCertificateAuthorityAuditReportOutcome> WaitUntilAuditReportCreated(
const Model::DescribeCertificateAuthorityAuditReportRequest& request) {
std::vector<Aws::Utils::Acceptor<Model::DescribeCertificateAuthorityAuditReportOutcome>> acceptors;
acceptors.push_back(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here you dont want to use push_back, you want to use emplace_back and construct the element in-place to avoid a unnecessary constructor. for instance consider the class

class widget {
 public:
  widget(std::string a, std::string b) { std::cout << "widget()" << std::endl; };
  widget(const widget& other) { std::cout << "widget(const widget&)" << std::endl; }
  widget(widget&& other) noexcept { std::cout << "widget(widget&&)" << std::endl; }
  widget& operator=(const widget& other) {
    std::cout << "widget& operator=(const widget&)" << std::endl;
    if (this == &other) return *this;
    return *this;
  }
  widget& operator=(widget&& other) noexcept {
    std::cout << "widget& operator=(widget&&)" << std::endl;
    if (this == &other) return *this;
    return *this;
  }
};

where we call push_back

std::vector<widget> widgets{};
widgets.push_back(widget{"a", "b"});

this will output

widget()
widget(widget&&)

because we call the constructor, then move construct the item, and infact clang-tidy will give us a warning about this

Clang-Tidy: Use emplace_back instead of push_back

which alludes to this you can instead call emplace back

widgets.emplace_back("a", "b");

which will only output

widget()

in this case that would look like

acceptors.emplace_back(Aws::Utils::WaiterState::SUCCESS, 
  Aws::Utils::MatcherType::PATH, 
  Aws::String("SUCCESS"),
  [](const Model::DescribeCertificateAuthorityAuditReportOutcome& outcome, const Aws::Utils::ExpectedValue& expected) {
    if (!outcome.IsSuccess()) return false;
      const auto& result = outcome.GetResult();
      return Model::AuditReportStatusMapper::GetNameForAuditReportStatus(result.GetAuditReportStatus()) == expected.get<Aws::String>();
  });

}});
acceptors.push_back({Aws::Utils::WaiterState::FAILURE, Aws::Utils::MatcherType::ERROR, Aws::String("AccessDeniedException")});

auto operation = [this](const Model::DescribeCertificateAuthorityAuditReportRequest& req) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need a intermediate lambda? could we just pass static_cast<DerivedClient*>(this)->DescribeCertificateAuthorityAuditReport to the waiter template?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. the function is associated with the client object and non-static so we either need a lambda or use std::bind.

#pragma once
#include <aws/account/AccountPaginationBase.h>
#include <aws/account/AccountServiceClientModel.h>
#include <aws/account/AccountWaiter.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does AccountWaiter.h exist? compilation should fail if thats the case

struct Acceptor {
WaiterState state;
MatcherType matcher;
ExpectedValue expected;
std::function<bool(const OutcomeT&, const ExpectedValue&)> pathMatcher;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so i think this goes back to what we talked about a long time ago, which is that MatcherType is tightly coupled with OutcomeT and ExpectedValue. i.e. we change what validation logic is ran in acceptor based on MatcherType. specifically for instance pathMatcher is not use unless MatcherType is PATH_* so it is "wasted space" on a error matcher to have the memory space for pathMatcher.

I think we can use inheritence to get rid of this and more accurately model the "we have different types of acceptors" from a higher view points.

if we had Acceptor as a interface

struct acceptor {
  virtual bool accept() = 0;
  virtual ~acceptor() = default;
};

we can have one "acceptor" implementation per MatcherTypei.e.

class error_acceptor: public acceptor {
public:
  error_acceptor() {
    // construct for error type
  }

  virtual bool accept() override {
    // test if the error matches    
  }
};

class path_acceptor: acceptor {
 public:
  path_acceptor() {
    // construct for path type
  }
  
  virtual bool accept() override {
    // jmes path function matches
  }
};

when then delegate acceptor type to code gen instead of runtime evaluation of MatcherType.

in waiter generation you could do something like

std::vector<std::unique_ptr<acceptor>> acceptors{};
acceptors.emplace_back(std::make_unique<error_acceptor>());
acceptors.emplace_back(std::make_unique<path_acceptor>());

then in Matches you just iterate the list of acceptors and call the vtable for accept

Shape afterLeft = walkFields(sub.getLeft(), current, model);
return afterLeft != null ? findFirstListElement(sub.getRight(), afterLeft, model) : null;
}
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whenever we return null we risk a null pointer exception, returning Optional<Shape> here is what we want to do, and force the caller to handle logic around a missing type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants