Skip to content

Behavior Traits

Juli Tera edited this page Apr 19, 2024 · 12 revisions

This wiki contains a mapping between Smithy Behavior traits and generated Ruby code.

idempotencyToken trait

Defines the input member of an operation that is used by the server to identify and discard replayed requests. Only a single member of the input of an operation can be targeted by the @idempotencyToken trait; only top-level structure members of the input of an operation are considered.

If a member with this trait is not provided, a token is automatically generated in the param builder. This is done so that any middleware will have access to a client token if none was provided.

structure MyOperationInput {
    @idempotencyToken
    clientToken: String,
}

The generated code is:

# params.rb
require 'securerandom'

class MyOperationInput
  def self.build(params, context:)
    Hearth::Validator.validate_types!(params, ::Hash, Types::MyOperationInput, context: context)
    type = Types::MyOperationInput.new
    Hearth::Validator.validate_unknown!(type, params, context: context) if params.is_a?(Hash)
    type.client_token = params[:client_token] || ::SecureRandom.uuid
    type
  end
end

See Smithy Documentation for more details.

idempotent trait

Indicates that the intended effect on the server of multiple identical requests with an operation is the same as the effect for a single such request. This trait does not influence code generation.

@idempotent
operation DeleteSomething {
    input: DeleteSomethingInput
    output: DeleteSomethingOutput
}

See Smithy Documentation for more details.

readonly trait

Indicates that an operation is effectively read-only. In Smithy, a GET operation is read only.

This trait does not influence code generation.

@readonly
operation GetSomething {
    input: GetSomethingInput
    output: GetSomethingOutput
}

See Smithy Documentation for more details.

retryable trait

Indicates that an error MAY be retried by the client. Hearth::ApiError defines retryable? and throttling? methods that return false by default. Errors marked with this trait will override the retryable? method to return true.

This trait also supports a throttling property, and if it is set to true, the throttling? method will similarly be overridden.

@error("server")
@retryable
@httpError(503)
structure ServiceUnavailableError {}

@error("client")
@retryable(throttling: true)
@httpError(429)
structure ThrottlingError {}

The generated code is:

# errors.rb

class ServiceUnavailableError < ApiServerError
  ...

  def retryable?
    true
  end
end
    
class ThrottlingError < ApiClientError
  ...

  def retryable?
    true
  end
      
  def throttling?
    true
  end
end

If the service returns an error, the retry middleware will call these two methods to determine if the error can be retried and determine whether or not it needs to apply backoff.

begin
  output = @app.call(input, context)
  raise output.error if output.error
rescue ApiError => e
  return output if !e.retryable? || attempt >= @max_attempts

  Kernel.sleep(backoff_with_jitter(attempt)) if e.throttling?
  attempt += 1
  retry
rescue Hearth::HTTP::NetworkingError => e
  ...
end

See Smithy Documentation for more details.

paginated trait

The @paginated trait indicates that an operation intentionally limits the number of results returned in a single response and that multiple invocations might be necessary to retrieve all results. Each paginated operation will have a pagination class that automatically iterates response pages by using the response’s output token as the next input token.

See Paginators section and Smithy Documentation.

requiredCompression trait

Indicates that an operation supports compressing requests from clients to services. Operation input members must not have both the @streaming trait and @requiresLength trait applied. This avoids the client reading and compressing the entire stream for the length of the compressed data to set the Content-Length header.

Currently supported compression algorithms are: "gzip".

@requestCompression(
    encodings: ["gzip"]
)
@http(method: "POST", uri: "/compress_operation")
operation MyCompressionOperation {
    input: MyCompressionInput,
}

@input
structure MyCompressionInput {
    @httpPayload
    data: String
}

// streaming operation
@requestCompression(
    encodings: ["gzip"]
)
@http(method: "POST", uri: "/streaming_compress_operation")
operation MyStreamingCompressionOperation {
    input: MyStreamingCompressionOperationInput,
}

@input
structure MyStreamingCompressionOperationInput {
    @required
    @httpPayload
    body: StreamingData
}

@streaming
blob StreamingData

The generated code is:

# builders.rb
class MyCompressionOperation
  def self.build(http_req, input:)
    http_req.body = StringIO.new(input[:data] || '')
  end
end

# streaming operation
class MyStreamingCompressionOperation
  def self.build(http_req, input:)
    http_req.body = input[:body]
    http_req.headers['Transfer-Encoding'] = 'chunked'
    http_req.headers['Content-Type'] = 'application/octet-stream'
  end
 end

See Smithy Documentation for more details.