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

fix!: Make config object immutable after client creation #903

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

jbelkins
Copy link
Contributor

@jbelkins jbelkins commented Feb 6, 2025

Issue #

Description of changes

Scope

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

self.plugins = defaultPlugins
func configureClient(clientConfiguration: inout ClientType.Config) async throws {
try await plugin.configureClient(clientConfiguration: &clientConfiguration)
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The PluginContainer class above is used to type-erase the actual plugin so that multiple plugins may be stored in a collection together.

public protocol Plugin<Config> {
associatedtype Config: ClientConfiguration

func configureClient(clientConfiguration: inout Config) async throws
Copy link
Contributor Author

@jbelkins jbelkins Feb 6, 2025

Choose a reason for hiding this comment

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

Plugin now takes an inout Config so that it may make permanent changes to the passed config.

The type of the config is now generic to the plugin so that additional casting is not needed on the config in order to access its properties.

config.retryStrategyOptions = self.retryStrategyOptions
}
public func configureClient(clientConfiguration: inout Config) {
clientConfiguration.retryStrategyOptions = self.retryStrategyOptions
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note there is no need to cast to DefaultClientConfiguration any more, because that constraint is built into the type of the plugin

@@ -55,6 +55,7 @@ class DefaultClientConfiguration : ClientConfiguration {
parameters = listOf(
FunctionParameter.NoLabel("provider", ClientRuntimeTypes.Core.InterceptorProvider)
),
isMutating = true,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Places the mutating modifier on these methods, since they now change properties of a value type


writer.unwrite(",\n").write("")
while (pluginsIterator.hasNext()) {
writer.write(".withPlugin(\$L)", pluginsIterator.next().customInitialization(writer))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rather than render the plugins as array elements, they are added with a builder method since Swift will reject a literal array for not having plugins of the same type.

@@ -113,7 +108,7 @@ open class HttpProtocolServiceClient(
.joinToString(" & ")

writer.openBlock(
"public class \$LConfiguration: \$L {", "}",
"public struct \$LConfiguration: \$L {", "}",
Copy link
Contributor Author

@jbelkins jbelkins Feb 6, 2025

Choose a reason for hiding this comment

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

This is the key change: client config is changed from a class to a struct. This allows config to be modified by plugins, etc. but to be "frozen" when assigned to an immutable property on the client, at client creation.

@@ -156,7 +151,7 @@ open class HttpProtocolServiceClient(
}

private fun renderEmptyAsynchronousConfigInitializer(properties: List<ConfigProperty>) {
writer.openBlock("public convenience required init() async throws {", "}") {
writer.openBlock("public init() async throws {", "}") {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In several places convenience and required are removed from config initializers since these modifiers are not used for value types.

fun customInitialization(writer: SwiftWriter) {
writer.writeInline("\$N()", className)
fun customInitialization(writer: SwiftWriter): String {
return writer.format("\$N()", className)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This method now creates the text with a writer & returns it to the caller.

name,
renderedParameters,
renderedAsync,
) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added the optional mutating modifier here & did a lot of code cleanup.

(renderedThrows and renderedReturnType remain interpolated because openBlock takes a max number of arguments.)

}
return clientConfiguration
return config
Copy link
Contributor Author

Choose a reason for hiding this comment

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

An empty config is created, it is modified by each plugin, then the final config is returned to the caller.

public init() {}

public func withPlugin<P: Plugin>(_ plugin: P) -> ClientBuilder<ClientType> where P.Config == ClientType.Config {
self.plugins.append(PluginContainer(plugin: plugin))
return self
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Plugins are added with a .withPlugin() method so that each can be placed in a type-erased container for storage in an array.

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.

1 participant