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 interceptors interfaces #685

Merged
merged 4 commits into from
Apr 25, 2024

Conversation

milesziemer
Copy link
Contributor

@milesziemer milesziemer commented Mar 22, 2024

Add interceptors interfaces to client libraries.

Interceptors defines hooks that can be used to inject functionality into different phases of the operation execution pipeline.

Description of changes

  • Adds the Interceptor protocol, which has methods defining the hooks into the operation execution pipeline. Protocol implementations can specify concrete types for the transport request/response types (i.e. SdkHttpRequest/HttpResponse), as well as a type that provides Attributes through the new HasAttributes protocol. This allows 'less specific' interceptors that don't care about the transport request/response types, or the available attributes, to be used with more specific interceptors. See an example in InterceptorTests
  • Adds HttpInterceptor, which inherits from Interceptor and specializes the request, response, and attributes types to be http specific. Interceptor implementations can use this protocol so they don't have to define RequestType, ResponseType, and AttributesType if they need all http specific stuff.
  • Adds the InterceptorContext protocol and various child protocols that define which properties of the interceptor context can be accessed at different points in the execution pipeline. Each interceptor hook accepts one of these child protocols as input. The context protocols have the same associated types as Interceptor, so implementations can define the types they will have in the context.
  • Adds the HasAttributes protocol, which is basically a protocol version of the Attributes struct, and implements it for HttpContext. This lets us wrap HttpContext into interceptor context objects, and use it in interceptor implementations, much like how it is used in existing middleware.

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.

@milesziemer milesziemer force-pushed the interceptors-interfaces branch 2 times, most recently from b92019e to ef8709a Compare March 22, 2024 20:12
Sources/ClientRuntime/Interceptor/AnyInterceptor.swift Outdated Show resolved Hide resolved
associatedtype AttributesType: HasAttributes

/// - Returns: The input for the operation being invoked.
func getInput() -> Any
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't the type of the input RequestType? Not sure why this return value is untyped.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

input and output are the types of the modeled operation input and output, respectively. RequestType and ResponseType are the transport message types, like SdkHttpRequest, HttpResponse

Copy link
Contributor

Choose a reason for hiding this comment

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

Should the modeled input type be an associated type of this protocol like the RequestType & ResponseType then? That way you can return the actual modeled input type, and you don't need the func getInput<T>() -> T? method below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

}

public func modifyBeforeTransmit(context: inout some MutableRequest<Self.RequestType, Self.AttributesType>) async throws {
let input: TestInput = context.getInput()!
Copy link
Contributor

Choose a reason for hiding this comment

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

Force unwrapping during a test will crash the entire test suite if the unwrapped value is nil.

Instead, use XCTUnwrap which will throw a Swift error and just fail the test being run:

let input: TestInput = try XCTUnwrap(context.getInput())

try await self.readBeforeExecution(context)
}

internal func modifyBeforeSerialization(context: inout InterceptorContextType) async throws {
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the purpose for making the context param inout in this & the other interceptor hooks?

InterceptorContextType is a typealias for DefaultInterceptorContext. DefaultInterceptorContext is a class, which is a Swift reference type.

The inout modifier means that the reference to context can be modified by the called method to point to a different context, which isn't what I think you want. (inout would be necessary if the passed context was a Swift value type such as struct and you wanted to change the struct's properties, but that is not the case here.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No purpose. Working on the orchestrator PR I realized it and just made InterceptorContext AnyObject, so there's no more inouts anywhere. Need to update this PR

Copy link
Contributor

@jbelkins jbelkins left a comment

Choose a reason for hiding this comment

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

See my comment on using an associated type on interceptor context.

Also fix all the lint errors (mostly lines need to be broken up.)

To run swiftlint locally, in the root of the smithy-swift project on your Mac:

$ brew install swiftlint
$ swiftlint

associatedtype AttributesType: HasAttributes

/// - Returns: The input for the operation being invoked.
func getInput() -> Any
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the modeled input type be an associated type of this protocol like the RequestType & ResponseType then? That way you can return the actual modeled input type, and you don't need the func getInput<T>() -> T? method below.

func getAttributes() -> AttributesType
}

public extension InterceptorContext {
Copy link
Contributor

Choose a reason for hiding this comment

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

See my comment above, we should make the modeled input type an associated type of InterceptorContext so this conditional cast is unneeded

Add interceptors interfaces to client libraries.

Interceptors defines hooks that can be used to inject functionality
into different phases of the operation execution pipeline.
Also use XCTUnwrap in tests
@milesziemer milesziemer merged commit b8c2a18 into smithy-lang:main Apr 25, 2024
12 checks passed
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