From 61564695767172c7b220b08f9a4ad33eb40f2141 Mon Sep 17 00:00:00 2001 From: Matt Muller Date: Sat, 29 Jun 2024 13:27:52 -0400 Subject: [PATCH] Global config for Hearth clients --- .../rails_json/lib/rails_json/client.rb | 6 +- .../lib/rails_json/plugins/global_config.rb | 21 +++++ .../rpcv2_cbor/lib/rpcv2_cbor/client.rb | 6 +- .../lib/rpcv2_cbor/plugins/default_config.rb | 37 +++++++++ .../lib/rpcv2_cbor/plugins/global_config.rb | 21 +++++ .../white_label/lib/white_label/client.rb | 2 + .../lib/white_label/plugins/global_config.rb | 23 ++++++ .../WhiteLabelTestIntegration.java | 2 +- .../ruby/codegen/DirectedRubyCodegen.java | 4 + .../ruby/codegen/GenerationContext.java | 15 ++-- .../amazon/smithy/ruby/codegen/Hearth.java | 4 + .../smithy/ruby/codegen/RubyCodeWriter.java | 2 +- .../smithy/ruby/codegen/RubyIntegration.java | 2 +- .../GlobalConfigPluginGenerator.java | 76 +++++++++++++++++++ hearth/lib/hearth.rb | 19 +++++ hearth/lib/hearth/config/resolver.rb | 60 ++++++++++++++- hearth/lib/hearth/configuration.rb | 4 + 17 files changed, 290 insertions(+), 14 deletions(-) create mode 100644 codegen/projections/rails_json/lib/rails_json/plugins/global_config.rb create mode 100644 codegen/projections/rpcv2_cbor/lib/rpcv2_cbor/plugins/default_config.rb create mode 100644 codegen/projections/rpcv2_cbor/lib/rpcv2_cbor/plugins/global_config.rb create mode 100644 codegen/projections/white_label/lib/white_label/plugins/global_config.rb create mode 100644 codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/GlobalConfigPluginGenerator.java diff --git a/codegen/projections/rails_json/lib/rails_json/client.rb b/codegen/projections/rails_json/lib/rails_json/client.rb index 2851dc469..fc9e58c83 100644 --- a/codegen/projections/rails_json/lib/rails_json/client.rb +++ b/codegen/projections/rails_json/lib/rails_json/client.rb @@ -9,12 +9,16 @@ require 'stringio' +require_relative 'plugins/global_config' + module RailsJson # A REST JSON service that sends JSON requests and responses. class Client < Hearth::Client # @api private - @plugins = Hearth::PluginList.new + @plugins = Hearth::PluginList.new([ + Plugins::GlobalConfig.new + ]) # @param [Hash] options # Options used to construct an instance of {Config} diff --git a/codegen/projections/rails_json/lib/rails_json/plugins/global_config.rb b/codegen/projections/rails_json/lib/rails_json/plugins/global_config.rb new file mode 100644 index 000000000..866fd90b6 --- /dev/null +++ b/codegen/projections/rails_json/lib/rails_json/plugins/global_config.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# WARNING ABOUT GENERATED CODE +# +# This file was code generated using smithy-ruby. +# https://github.com/smithy-lang/smithy-ruby +# +# WARNING ABOUT GENERATED CODE + +module RailsJson + module Plugins + class GlobalConfig + def call(config) + options = config.options + ::Hearth.config.each do |key, value| + config[key] = value unless options.key?(key) + end + end + end + end +end diff --git a/codegen/projections/rpcv2_cbor/lib/rpcv2_cbor/client.rb b/codegen/projections/rpcv2_cbor/lib/rpcv2_cbor/client.rb index 2e1b4d5ad..d23698254 100644 --- a/codegen/projections/rpcv2_cbor/lib/rpcv2_cbor/client.rb +++ b/codegen/projections/rpcv2_cbor/lib/rpcv2_cbor/client.rb @@ -9,11 +9,15 @@ require 'stringio' +require_relative 'plugins/global_config' + module Rpcv2Cbor class Client < Hearth::Client # @api private - @plugins = Hearth::PluginList.new + @plugins = Hearth::PluginList.new([ + Plugins::GlobalConfig.new + ]) # @param [Hash] options # Options used to construct an instance of {Config} diff --git a/codegen/projections/rpcv2_cbor/lib/rpcv2_cbor/plugins/default_config.rb b/codegen/projections/rpcv2_cbor/lib/rpcv2_cbor/plugins/default_config.rb new file mode 100644 index 000000000..f3a3b2b4c --- /dev/null +++ b/codegen/projections/rpcv2_cbor/lib/rpcv2_cbor/plugins/default_config.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# WARNING ABOUT GENERATED CODE +# +# This file was code generated using smithy-ruby. +# https://github.com/smithy-lang/smithy-ruby +# +# WARNING ABOUT GENERATED CODE + +module Rpcv2Cbor + module Plugins + class DefaultConfig + def call(config) + end + + private + + def defaults + { + auth_resolver: [Auth::Resolver.new], + auth_schemes: [Auth::SCHEMES], + disable_host_prefix: [false], + endpoint: [proc { |cfg| cfg[:stub_responses] ? 'http://localhost' : nil }], + endpoint_resolver: [Endpoint::Resolver.new], + http_client: [proc { |cfg| Hearth::HTTP::Client.new(logger: cfg[:logger]) }], + interceptors: [Hearth::InterceptorList.new], + logger: [Logger.new(IO::NULL)], + plugins: [Hearth::PluginList.new], + retry_strategy: [Hearth::Retry::Standard.new], + stub_responses: [false], + stubs: [Hearth::Stubs.new], + validate_input: [true] + }.freeze + end + end + end +end diff --git a/codegen/projections/rpcv2_cbor/lib/rpcv2_cbor/plugins/global_config.rb b/codegen/projections/rpcv2_cbor/lib/rpcv2_cbor/plugins/global_config.rb new file mode 100644 index 000000000..9cade89ef --- /dev/null +++ b/codegen/projections/rpcv2_cbor/lib/rpcv2_cbor/plugins/global_config.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# WARNING ABOUT GENERATED CODE +# +# This file was code generated using smithy-ruby. +# https://github.com/smithy-lang/smithy-ruby +# +# WARNING ABOUT GENERATED CODE + +module Rpcv2Cbor + module Plugins + class GlobalConfig + def call(config) + options = config.options + ::Hearth.config.each do |key, value| + config[key] = value unless options.key?(key) + end + end + end + end +end diff --git a/codegen/projections/white_label/lib/white_label/client.rb b/codegen/projections/white_label/lib/white_label/client.rb index fbdef2be6..0729b13d3 100644 --- a/codegen/projections/white_label/lib/white_label/client.rb +++ b/codegen/projections/white_label/lib/white_label/client.rb @@ -9,6 +9,7 @@ require 'stringio' +require_relative 'plugins/global_config' require_relative 'plugins/test_plugin' module WhiteLabel @@ -29,6 +30,7 @@ class Client < Hearth::Client # @api private @plugins = Hearth::PluginList.new([ + Plugins::GlobalConfig.new, Plugins::TestPlugin.new ]) diff --git a/codegen/projections/white_label/lib/white_label/plugins/global_config.rb b/codegen/projections/white_label/lib/white_label/plugins/global_config.rb new file mode 100644 index 000000000..cc587a93d --- /dev/null +++ b/codegen/projections/white_label/lib/white_label/plugins/global_config.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# WARNING ABOUT GENERATED CODE +# +# This file was code generated using smithy-ruby. +# https://github.com/smithy-lang/smithy-ruby +# +# WARNING ABOUT GENERATED CODE + +module WhiteLabel + module Plugins + class GlobalConfig + def call(config) + options = config.options + ::Hearth.config.each do |key, value| + require 'byebug' + byebug + config[key] = value unless options.key?(key) + end + end + end + end +end diff --git a/codegen/smithy-ruby-codegen-test-utils/src/main/java/software/amazon/smithy/ruby/codegen/integrations/WhiteLabelTestIntegration.java b/codegen/smithy-ruby-codegen-test-utils/src/main/java/software/amazon/smithy/ruby/codegen/integrations/WhiteLabelTestIntegration.java index adfc84250..f955344ac 100644 --- a/codegen/smithy-ruby-codegen-test-utils/src/main/java/software/amazon/smithy/ruby/codegen/integrations/WhiteLabelTestIntegration.java +++ b/codegen/smithy-ruby-codegen-test-utils/src/main/java/software/amazon/smithy/ruby/codegen/integrations/WhiteLabelTestIntegration.java @@ -42,7 +42,7 @@ public boolean includeFor(ServiceShape service, Model model) { } @Override - public List getRuntimePlugins(GenerationContext context) { + public List getAdditionalRuntimePlugins(GenerationContext context) { return List.of(RubyRuntimePlugin.builder() .rubySource("plugins/test_plugin.rb") .pluginClass("Plugins::TestPlugin") diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/DirectedRubyCodegen.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/DirectedRubyCodegen.java index 200b506d7..b9daa8320 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/DirectedRubyCodegen.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/DirectedRubyCodegen.java @@ -42,6 +42,7 @@ import software.amazon.smithy.ruby.codegen.generators.ConfigGenerator; import software.amazon.smithy.ruby.codegen.generators.EndpointGenerator; import software.amazon.smithy.ruby.codegen.generators.GemspecGenerator; +import software.amazon.smithy.ruby.codegen.generators.GlobalConfigPluginGenerator; import software.amazon.smithy.ruby.codegen.generators.HttpProtocolTestGenerator; import software.amazon.smithy.ruby.codegen.generators.MiddlewareGenerator; import software.amazon.smithy.ruby.codegen.generators.ModuleGenerator; @@ -166,6 +167,9 @@ public void generateService(GenerateServiceDirective getRubyDependencies() { * @return Set of all RubyRuntimePlugins from all integrations */ public Set getRuntimePlugins() { - return integrations.stream() - .map((i) -> i.getRuntimePlugins(this)) - .flatMap(List::stream) - .collect(Collectors.toUnmodifiableSet()); + Set runtimePlugins = new LinkedHashSet<>(); + runtimePlugins.add(RubyRuntimePlugin.builder() + .pluginClass("Plugins::GlobalConfig") + .writeAdditionalFiles(context -> Collections.singletonList("plugins/global_config.rb")) + .build()); + integrations.forEach((i) -> { + runtimePlugins.addAll(i.getAdditionalRuntimePlugins(this)); + }); + return Collections.unmodifiableSet(runtimePlugins); } - /** * @return Set of AuthSchemes that apply to this service. */ diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/Hearth.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/Hearth.java index b12cc0293..b168cab3f 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/Hearth.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/Hearth.java @@ -22,6 +22,10 @@ */ public final class Hearth { + public static final Symbol HEARTH = Symbol.builder() + .name("Hearth") + .build(); + public static final Symbol CLIENT = Symbol.builder() .namespace("Hearth", "::") .name("Client") diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/RubyCodeWriter.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/RubyCodeWriter.java index c6c99a6cb..bdf36d71f 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/RubyCodeWriter.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/RubyCodeWriter.java @@ -129,7 +129,7 @@ public RubyCodeWriter preamble() { } /** - * Require statments for symbols/dependenices used + * Require statements for symbols/dependencies used * will be included in the generated code. * This should be called for writers that are used to generate full files. * diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/RubyIntegration.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/RubyIntegration.java index 0caaa164c..d2267c8f3 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/RubyIntegration.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/RubyIntegration.java @@ -81,7 +81,7 @@ default List getAdditionalClientConfig(GenerationContext context) * @param context - Generation context to process within * @return List of RubyRuntimePlugins */ - default List getRuntimePlugins(GenerationContext context) { + default List getAdditionalRuntimePlugins(GenerationContext context) { return Collections.emptyList(); } diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/GlobalConfigPluginGenerator.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/GlobalConfigPluginGenerator.java new file mode 100644 index 000000000..5925e1cb0 --- /dev/null +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/GlobalConfigPluginGenerator.java @@ -0,0 +1,76 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.ruby.codegen.generators; + +import java.nio.file.Paths; +import java.util.logging.Logger; +import software.amazon.smithy.codegen.core.directed.ContextualDirective; +import software.amazon.smithy.ruby.codegen.GenerationContext; +import software.amazon.smithy.ruby.codegen.Hearth; +import software.amazon.smithy.ruby.codegen.RubyCodeWriter; +import software.amazon.smithy.ruby.codegen.RubyFormatter; +import software.amazon.smithy.ruby.codegen.RubySettings; +import software.amazon.smithy.utils.SmithyInternalApi; + +/** + * Generate Config class for a Client. + */ +@SmithyInternalApi +public class GlobalConfigPluginGenerator extends RubyGeneratorBase { + private static final Logger LOGGER = + Logger.getLogger(ConfigGenerator.class.getName()); + + public GlobalConfigPluginGenerator( + ContextualDirective directive) { + super(directive); + } + + @Override + protected String getModule() { + return "Plugins"; + } + + @Override + public String rbFile() { + return Paths.get(settings.getGemName(), "lib", settings.getGemName(), + RubyFormatter.toSnakeCase(getModule()), "global_config.rb").toString(); + } + + public void render() { + write(writer -> { + writer + .preamble() + .includeRequires() + .addModule(settings.getModule()) + .addModule("Plugins") + .openBlock("class GlobalConfig") + .call(() -> renderCallMethod(writer)) + .closeBlock("end") + .closeAllModules(); + }); + LOGGER.fine("Wrote config defaults plugin to " + rbFile()); + } + + private void renderCallMethod(RubyCodeWriter writer) { + writer + .openBlock("def call(config)") + .write("options = config.options") + .openBlock("$T.config.each do |key, value|", Hearth.HEARTH) + .write("config[key] = value unless options.key?(key)") + .closeBlock("end") + .closeBlock("end"); + } +} diff --git a/hearth/lib/hearth.rb b/hearth/lib/hearth.rb index 94f476198..a0057f0a0 100755 --- a/hearth/lib/hearth.rb +++ b/hearth/lib/hearth.rb @@ -53,6 +53,25 @@ require_relative 'hearth/waiters/waiter' require_relative 'hearth/xml' +# Hearth is a low-level Ruby component library for code generated clients using +# the Smithy modeling language. module Hearth VERSION = File.read(File.expand_path('../VERSION', __dir__)).strip + + @config = {} + + class << self + # @return [Hash] Returns a hash of default configuration options shared + # by all constructed clients. + attr_reader :config + + # @param [Hash] config + def config=(config) + unless config.is_a?(Hash) + raise ArgumentError, 'configuration must be a hash' + end + + @config = config + end + end end diff --git a/hearth/lib/hearth/config/resolver.rb b/hearth/lib/hearth/config/resolver.rb index a0da067a0..a73116325 100644 --- a/hearth/lib/hearth/config/resolver.rb +++ b/hearth/lib/hearth/config/resolver.rb @@ -1,5 +1,58 @@ # frozen_string_literal: true +# module Hearth +# # @api private +# module Config +# # Resolves configuration defaults. +# class Resolver +# private_class_method :new +# +# # @param config [Struct] +# # @param defaults [Hash] +# # @return [Struct] +# def self.resolve(config, defaults = {}) +# new(config).send(:resolve, defaults) +# end +# +# def [](key) +# unless @resolved[key] +# @config[key] = resolve_default(key) +# @resolved[key] = true +# end +# @config[key] +# end +# +# private +# +# # @param config [Struct] +# def initialize(config) +# @config = config +# @resolved = {} +# end +# +# def resolve(defaults) +# @defaults = defaults +# @config.members.each do |key| +# @config[key] = self[key] +# end +# end +# +# def resolve_default(key) +# @defaults[key]&.each do |default| +# value = +# if default.respond_to?(:call) +# default.call(self) +# else +# default +# end +# return value unless value.nil? +# end +# nil +# end +# end +# end +# end + module Hearth # @api private module Config @@ -15,11 +68,10 @@ def self.resolve(config, options, defaults = {}) new(config).send(:resolve, options, defaults) end - def key(key) + def [](key) @options[key] = resolve_default(key) unless @options.key?(key) @options[key] end - alias [] key private @@ -29,10 +81,10 @@ def initialize(config) end def resolve(options, defaults) - @options = options + @options = options.dup @defaults = defaults @config.members.each do |key| - @config[key] = key(key) + @config[key] = self[key] end end diff --git a/hearth/lib/hearth/configuration.rb b/hearth/lib/hearth/configuration.rb index ad7989ea3..cd6472b25 100644 --- a/hearth/lib/hearth/configuration.rb +++ b/hearth/lib/hearth/configuration.rb @@ -4,10 +4,14 @@ module Hearth # A module mixed into Config structs that resolves default value providers. module Configuration def initialize(**options) + @options = options Hearth::Config::Resolver.resolve(self, options, _defaults) super end + # @return [Hash] the original configuration options. + attr_accessor :options + def merge(configuration) self.class.new(**to_h.merge(configuration.to_h)) end